source: trunk/launch/launch/main.c @ 268

Last change on this file since 268 was 268, checked in by Nicholas Riley, 14 years ago

main.c: Remove extra debugging statement.

File size: 35.9 KB
Line 
1/*
2 launch - a smarter 'open' replacement
3 Nicholas Riley <launchsw@sabi.net>
4
5 Copyright (c) 2001-06, Nicholas Riley
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9
10 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
12 * Neither the name of this software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 
16*/
17
18#define BROKEN_AUTHORIZATION 1
19#define kComponentSignatureString "launch"
20
21#include <unistd.h>
22#include <sys/stat.h>
23#include <Carbon/Carbon.h>
24#include <CoreServices/CoreServices.h>
25#include <CoreFoundation/CoreFoundation.h>
26#include <ApplicationServices/ApplicationServices.h>
27
28#ifndef BROKEN_AUTHORIZATION
29#include <Security/Authorization.h>
30#include <Security/AuthorizationTags.h>
31#endif
32
33const char *APP_NAME;
34
35#define VERSION "1.1d2"
36
37#define STRBUF_LEN 1024
38#define ACTION_DEFAULT ACTION_OPEN
39
40struct {
41    OSType creator;
42    CFStringRef bundleID, name;
43    Boolean forceURLs;
44    enum { ACTION_FIND, ACTION_FIND_ITEMS,
45           ACTION_OPEN, ACTION_OPEN_ITEMS, 
46           ACTION_INFO_ITEMS, ACTION_LAUNCH_URLS } action;
47} OPTS = 
48{
49    kLSUnknownCreator, NULL, NULL, false,
50    ACTION_DEFAULT
51};
52
53#define DEFAULT_LAUNCH_FLAGS (kLSLaunchNoParams | kLSLaunchStartClassic | kLSLaunchAsync)
54
55LSApplicationParameters LPARAMS = {0, DEFAULT_LAUNCH_FLAGS, NULL, NULL, NULL, NULL, NULL};
56CFArrayRef ITEMS = NULL;
57FSRef APPLICATION;
58
59char *TEMPFILE = NULL;
60
61typedef struct {
62    OSStatus status;
63    const char *desc;
64} errRec, errList[];
65
66static errList ERRS = {
67    // Launch Services errors
68    { kLSUnknownErr, "unknown Launch Services error" },
69    { kLSApplicationNotFoundErr, "application not found" },
70    { kLSLaunchInProgressErr, "application is being opened; please try again after the application is open" },
71    { kLSNotRegisteredErr, "application not registered in Launch Services database" },
72    { kLSNoExecutableErr, "application package contains no executable, or an unusable executable" },
73    { kLSNoClassicEnvironmentErr, "Classic environment required but not available" },
74    { kLSMultipleSessionsNotSupportedErr, "unable to launch multiple instances of application" },
75#ifndef BROKEN_AUTHORIZATION
76    // Security framework errors
77    { errAuthorizationDenied, "authorization denied" },
78    { errAuthorizationCanceled, "authentication was cancelled" },
79#endif
80    // Internet Config errors
81    { icPrefNotFoundErr, "no helper application is defined for the URL's scheme" },
82    { icNoURLErr, "not a URL" },
83    { icInternalErr, "internal Internet Config error" },
84    // Misc. errors
85    { nsvErr, "the volume cannot be found (buggy filesystem?)" },
86    { procNotFound, "unable to connect to system service.\nAre you logged in?" },
87    { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" },
88    { kCGErrorApplicationRequiresNewerSystem, "application requires a newer Mac OS X version" },
89    { fnfErr, "file not found" },
90    { 0, NULL }
91};
92
93void usage() {
94    fprintf(stderr, "usage: %s [-npswbmhCXLU] [-c creator] [-i bundleID] [-u URL] [-a name] [-o argument] [item ...] [-]\n"
95                    "   or: %s [-npflswbmhCXLU] [-o argument] item ...\n", APP_NAME, APP_NAME);
96    fprintf(stderr,
97        "  -n            print matching paths/URLs instead of opening them\n"
98        "  -p            ask application(s) to print document(s)\n"
99        "  -f            display information about item(s)\n"
100        "  -l            launch URLs (e.g. treat http:// URLs as Web sites, not WebDAV)\n"
101#ifndef BROKEN_AUTHORIZATION
102        "  -s            launch target(s) as superuser (authenticating if needed)\n"
103#endif
104        "  -w            wait for application to finish opening before exiting\n"
105        "  -b            launch application in the background\n"
106        "  -m            launch application again, even if already running\n"
107        "  -h            hide application once it's finished opening\n"
108        "  -C            force CFM/PEF Carbon application to launch in Classic\n"
109        "  -X            don't start Classic for this app if Classic isn't running\n"
110        "  -L            suppress normal opening behavior (e.g. untitled window)\n"
111        "  -U            interpret items as URLs, even if same-named files exist\n"
112        "  -c creator    match application by four-character creator code ('ToyS')\n"
113        "  -i bundle ID  match application by bundle identifier (com.apple.scripteditor)\n"
114        "  -u URL        open application at file:// URL (NOT RECOMMENDED for scripts)\n"
115        "  -a name|path  match application by name/path (NOT RECOMMENDED, very fragile)\n"
116        "  -o argument   pass argument to application (may be specified more than once)\n"
117        "'document' may be a file, folder, or disk - whatever the application can open.\n"
118        "'item' may be a file, folder, disk, or URL.\n\n");
119    fprintf(stderr, "launch "VERSION" (c) 2001-06 Nicholas Riley <http://web.sabi.net/nriley/software/>.\n"
120                    "Please send bugs, suggestions, etc. to <launchsw@sabi.net>.\n");
121
122    exit(1);
123}
124
125char *osstatusstr(OSStatus err) {
126    errRec *rec;
127    const char *errDesc = "unknown error";
128    char * const failedStr = "(unable to retrieve error message)";
129    static char *str = NULL;
130    size_t len;
131    if (str != NULL && str != failedStr) free(str);
132    for (rec = &(ERRS[0]) ; rec->status != 0 ; rec++)
133        if (rec->status == err) {
134            errDesc = rec->desc;
135            break;
136        }
137    len = strlen(errDesc) + 10 * sizeof(char);
138    str = (char *)malloc(len);
139    if (str != NULL)
140        snprintf(str, len, "%s (%ld)", errDesc, err);
141    else
142        str = failedStr;
143    return str;
144}
145
146void osstatusexit(OSStatus err, const char *fmt, ...) {
147    va_list ap;
148    const char *errDesc = osstatusstr(err);
149    va_start(ap, fmt);
150    fprintf(stderr, "%s: ", APP_NAME);
151    vfprintf(stderr, fmt, ap);
152    fprintf(stderr, ": %s\n", errDesc);
153    exit(1);
154}
155
156void errexit(const char *fmt, ...) {
157    va_list ap;
158    va_start(ap, fmt);
159    fprintf(stderr, "%s: ", APP_NAME);
160    vfprintf(stderr, fmt, ap);
161    fprintf(stderr, "\n");
162    exit(1);
163}
164
165#ifndef BROKEN_AUTHORIZATION
166
167Boolean authenticated(AuthorizationItem item, AuthorizationRef *pAuthRef) {
168    AuthorizationRights rights;
169    AuthorizationRights *authorizedRights;
170    AuthorizationFlags flags;
171    OSStatus err;
172
173    // Create an AuthorizationRef yet with the kAuthorizationFlagDefaults
174    // flags to get the user's current authorization rights.
175    rights.count = 0;
176    rights.items = NULL;
177   
178    flags = kAuthorizationFlagDefaults;
179
180    err = AuthorizationCreate(&rights,
181        kAuthorizationEmptyEnvironment, flags,
182        pAuthRef);
183       
184    rights.count = 1;
185    rights.items = &item;
186   
187    flags = kAuthorizationFlagExtendRights;
188   
189    // don't ask for a password, just return failure if no rights
190    err = AuthorizationCopyRights(*pAuthRef, &rights,
191        kAuthorizationEmptyEnvironment, flags, &authorizedRights);
192
193    switch (err) {
194    case errAuthorizationSuccess:
195        // we don't need these items, and they need to be disposed of
196        AuthorizationFreeItemSet(authorizedRights);
197        return true;
198    case errAuthorizationInteractionNotAllowed:
199        return false;
200    default:
201        osstatusexit(err, "unable to determine authentication status");
202    }
203    return false; // to satisfy compiler
204}
205
206void authenticate(AuthorizationItem item, AuthorizationRef authorizationRef) {
207    AuthorizationRights rights = {1, &item};
208    AuthorizationRights *authorizedRights;
209    AuthorizationFlags flags;
210    OSStatus err;
211
212    flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
213
214    // Here, since we've specified kAuthorizationFlagExtendRights and
215    // have also specified kAuthorizationFlagInteractionAllowed, if the
216    // user isn't currently authorized to execute tools as root
217    // (kAuthorizationRightExecute), they will be asked for their password.
218    // The err return value will indicate authorization success or failure.
219    err = AuthorizationCopyRights(authorizationRef,&rights,
220                        kAuthorizationEmptyEnvironment,
221                        flags,&authorizedRights);
222   
223    if (errAuthorizationSuccess == err)
224        AuthorizationFreeItemSet(authorizedRights);
225    else
226        osstatusexit(err, "unable to authenticate");
227}
228#endif
229
230CFURLRef normalizedURLFromString(CFStringRef str) {
231    CFURLRef url = CFURLCreateWithString(NULL, str, NULL);
232    if (url != NULL) {
233        CFURLRef absURL = CFURLCopyAbsoluteURL(url);
234        CFRelease(url);
235        url = NULL;
236        if (absURL != NULL) {
237            CFStringRef scheme = CFURLCopyScheme(absURL);
238            url = absURL;
239            if (scheme == NULL) {
240                CFRelease(url);
241                url = NULL;
242            } else {
243                CFRelease(scheme);
244            }
245        }
246    }
247    return url;
248}
249
250CFURLRef normalizedURLFromPrefixSlack(CFStringRef prefix, CFStringRef slackStr) {
251    CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"),
252                                               prefix, slackStr);
253    CFURLRef normalizedURL = normalizedURLFromString(str);
254    CFRelease(str);
255    return normalizedURL;
256}
257
258char *tempFile(int *fd) {
259    char *tmpDir = getenv("TMPDIR");
260    const char * const tempTemplate = "/launch-stationery-XXXXXXXX";
261    char *tempPath;
262    OSStatus err;
263    FSRef fsr;
264    FSCatalogInfo catalogInfo;
265    FileInfo *fInfo;
266
267    // create temporary file
268    if (tmpDir == NULL) tmpDir = "/tmp";
269    tempPath = (char *)malloc(strlen(tmpDir) + strlen(tempTemplate) + 1);
270    if (tempPath == NULL) errexit("can't allocate memory");
271    strcpy(tempPath, tmpDir);
272    strcat(tempPath, tempTemplate);
273    if ( (*fd = mkstemp(tempPath)) == -1)
274        errexit("can't create temporary file '%s'", tempPath);
275    // mark file as stationery
276    err = FSPathMakeRef((UInt8 *)tempPath, &fsr, NULL);
277    if (err != noErr) osstatusexit(err, "can't find '%s'", tempPath);
278    err = FSGetCatalogInfo(&fsr, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL);
279    if (err != noErr) osstatusexit(err, "can't get information for '%s'", tempPath);
280    fInfo = (FileInfo *)&(catalogInfo.finderInfo);
281    fInfo->finderFlags |= kIsStationery;
282    err = FSSetCatalogInfo(&fsr, kFSCatInfoFinderInfo, &catalogInfo);
283    if (err != noErr) osstatusexit(err, "can't set information for '%s'", tempPath);
284   
285    return tempPath;
286}
287
288char *stdinAsTempFile() {
289    unsigned char *buf;
290    int bufsize;
291    // Actual number of characters read, and therefore written.
292    ssize_t charCount;
293    int fd;
294    struct stat stat_buf;
295    char *tempFilePath;
296
297    tempFilePath = tempFile(&fd);
298
299    if (fstat(fd, &stat_buf) == -1)
300        errexit("can't fstat temporary file '%s'", tempFilePath);
301
302    bufsize = stat_buf.st_blksize;
303    if ( (buf = (unsigned char *)malloc(bufsize * sizeof(unsigned char))) == NULL)
304        errexit("can't allocate %ld bytes of buffer memory",
305                bufsize * sizeof(unsigned char));
306
307    // Loop until the end of the file.
308    while (1) {
309        // Read a block of input.
310        charCount = read(STDIN_FILENO, buf, bufsize);
311        if (charCount < 0) {
312            errexit("can't read from standard input");
313        }
314        // End of this file?
315        if (charCount == 0)
316            break;
317        // Write this block out.
318        if (write(fd, buf, charCount) != charCount)
319            errexit("error writing to file '%s'", tempFilePath);
320    }
321    free(buf);
322    return tempFilePath;
323}
324
325void getargs(int argc, char * const argv[]) {
326    extern char *optarg;
327    extern int optind;
328    int ch;
329    OSStatus err;
330    Boolean appSpecified = false;
331
332    if (argc == 1) usage();
333   
334    while ( (ch = getopt(argc, argv, "npflswbmhCXLUc:i:u:a:o:")) != -1) {
335        switch (ch) {
336        case 'n':
337            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
338            OPTS.action = ACTION_FIND;
339            break;
340        case 'p':
341            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
342            OPTS.action = ACTION_OPEN;
343            LPARAMS.flags |= kLSLaunchAndPrint;
344            break;
345        case 'f':
346            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
347            OPTS.action = ACTION_INFO_ITEMS;
348            break;
349        case 'l':
350            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
351            OPTS.action = ACTION_LAUNCH_URLS;
352            break;
353        case 's':
354#ifdef BROKEN_AUTHORIZATION
355            errexit("-s option no longer functional after 10.1 Security Update, sorry");
356#else
357        {
358            AuthorizationRef authRef;
359            AuthorizationItem item = { kAuthorizationRightExecute, strlen(argv[0]), argv[0], 0 };
360            OSStatus err;
361           
362            if (authenticated(item, &authRef)) {
363                continue;
364            }
365            authenticate(item, authRef);
366            err = AuthorizationExecuteWithPrivileges(authRef, argv[0], 0, &argv[1], NULL);
367            if (err != noErr) osstatusexit(err, "unable to launch '%s' with superuser privileges", argv[0]);
368            exit(0); // XXX exit status propagate?
369        }
370#endif
371        case 'w': LPARAMS.flags ^= kLSLaunchAsync; break;      // synchronous
372        case 'b': LPARAMS.flags |= kLSLaunchDontSwitch; break; // open in background
373        case 'm': LPARAMS.flags |= kLSLaunchNewInstance; break;// open multiple
374        case 'h': LPARAMS.flags |= kLSLaunchAndHide; break;    // hide once launched
375        case 'C': LPARAMS.flags |= kLSLaunchInClassic; break;  // force Classic
376        case 'X': LPARAMS.flags ^= kLSLaunchStartClassic; break;// don't start Classic for app
377        case 'L':
378        {
379            OSStatus err;
380            LPARAMS.initialEvent = malloc(sizeof(AppleEvent));
381            err = AECreateAppleEvent(kASAppleScriptSuite, kASLaunchEvent, NULL, kAutoGenerateReturnID, kAnyTransactionID, LPARAMS.initialEvent);
382            if (err != noErr) osstatusexit(err, "unable to construct launch Apple Event", argv[0]);
383        }
384        case 'U': OPTS.forceURLs = true; break;
385        case 'c':
386            if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long");
387            OPTS.creator = *(OSTypePtr)optarg;
388            appSpecified = true;
389            break;
390        case 'i':
391            OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
392            appSpecified = true;
393            break;
394        case 'a':
395            err = FSPathMakeRef((UInt8 *)optarg, &APPLICATION, NULL);
396            if (err == noErr) {
397                LPARAMS.application = &APPLICATION;
398            } else {
399                OPTS.name = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
400            }
401            appSpecified = true;
402            break;
403        case 'u':
404            { CFStringRef str = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
405              CFURLRef appURL = CFURLCreateWithString(NULL, str, NULL);
406              if (appURL == NULL)
407                  errexit("invalid URL (argument of -u)");
408              err = CFURLGetFSRef(appURL, &APPLICATION);
409              if (err != noErr)
410                  osstatusexit(err, "can't find application (argument of -u)");
411            }
412            LPARAMS.application = &APPLICATION;
413            appSpecified = true;
414            break;
415        case 'o':
416            if (LPARAMS.argv == NULL)
417                LPARAMS.argv = CFArrayCreateMutable(NULL, 0, NULL);
418            CFArrayAppendValue((CFMutableArrayRef)LPARAMS.argv,
419                               CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8));
420            break;
421        default: usage();
422        }
423    }
424   
425    argc -= optind;
426    argv += optind;
427   
428    if ( (OPTS.action == ACTION_FIND || OPTS.action == ACTION_LAUNCH_URLS ||
429          OPTS.action == ACTION_INFO_ITEMS) && LPARAMS.flags != DEFAULT_LAUNCH_FLAGS)
430        errexit("options -s, -b, -m, -h, -C, -X apply to application launch (not -n, -f or -l)");
431   
432    if (OPTS.creator == kLSUnknownCreator && OPTS.bundleID == NULL && OPTS.name == NULL) {
433        if (argc == 0 && LPARAMS.application == NULL)
434            errexit("must specify an application by -u, or one or more of -c, -i, -a");
435        if (!appSpecified) {
436            if (OPTS.action == ACTION_FIND)
437                OPTS.action = ACTION_FIND_ITEMS;
438            if (OPTS.action == ACTION_OPEN)
439                OPTS.action = ACTION_OPEN_ITEMS;
440        }
441    } else {
442        if (LPARAMS.application != NULL)
443            errexit("application URL (argument of -u) incompatible with matching by -c, -i, -a");
444    }
445
446    if (OPTS.action == ACTION_LAUNCH_URLS && appSpecified)
447        errexit("launching URLs with a given application is not supported; try without -l");
448
449    if (OPTS.action == ACTION_INFO_ITEMS && appSpecified)
450        errexit("can't get information (-f) on item(s) using an application (-u, -c, -i, -a)");
451
452    if (argc == 0 && OPTS.action == ACTION_OPEN && LPARAMS.flags & kLSLaunchAndPrint)
453        errexit("print option (-p) must be accompanied by document(s) to print");
454   
455    if (argc != 0) {
456        int i;
457        OSStatus err;
458        CFStringRef argStr;
459        CFURLRef itemURL;
460        LSItemInfoRecord docInfo;
461
462        if (OPTS.action == ACTION_FIND)
463            errexit("application with documents only supported for open or print, not find");
464
465        // handle document/item/URL arguments
466        ITEMS = CFArrayCreateMutable(NULL, argc, NULL);
467        for (i = 0 ; i < argc ; i++) {
468            argStr = NULL;
469            if (strcmp(argv[i], "-") == 0) {
470                TEMPFILE = stdinAsTempFile();
471                itemURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)TEMPFILE, strlen(TEMPFILE), false);
472                LPARAMS.flags ^= kLSLaunchAsync;
473            } else {
474                struct stat stat_buf;
475                if (!OPTS.forceURLs && stat(argv[i], &stat_buf) == 0) {
476                    itemURL = NULL;
477                } else {
478                    argStr = CFStringCreateWithCString(NULL, argv[i], kCFStringEncodingUTF8);
479                    // check for URLs
480                    itemURL = normalizedURLFromString(argStr);
481                    if (itemURL == NULL && OPTS.action == ACTION_LAUNCH_URLS) {
482                        // check for email addresses
483                        if (strchr(argv[i], '@') != NULL && strchr(argv[i], '/') == NULL)
484                            itemURL = normalizedURLFromPrefixSlack(CFSTR("mailto:"), argStr);
485                        // check for "slack" URLs
486                        if (itemURL == NULL && strchr(argv[i], '.') != NULL && strchr(argv[i], '/') != argv[i])
487                            itemURL = normalizedURLFromPrefixSlack(CFSTR("http://"), argStr);
488                    }
489                }
490                if (itemURL == NULL) {
491                    // check for file paths
492                    itemURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)argv[i], strlen(argv[i]), false);
493                    err = LSCopyItemInfoForURL(itemURL, kLSRequestExtensionFlagsOnly, &docInfo);
494                    if (err != noErr) osstatusexit(err, "unable to locate '%s'", argv[i]);
495                }
496            }
497            CFArrayAppendValue((CFMutableArrayRef)ITEMS, itemURL);
498            // don't CFRelease the itemURL because CFArray doesn't retain it by default
499            if (argStr != NULL) CFRelease(argStr);
500        }
501    }
502}
503
504Boolean stringFromURLIsRemote(CFURLRef url, char *strBuffer) {
505    CFStringRef scheme = CFURLCopyScheme(url);
506    Boolean isRemote = !CFEqual(scheme, CFSTR("file"));
507    CFRelease(scheme);
508   
509    strBuffer[0] = '\0';
510    if (isRemote) {
511        CFStringRef urlString = CFURLGetString(url);
512        CFStringGetCString(urlString, strBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
513        CFRelease(urlString);
514    } else {
515        if (CFURLGetFileSystemRepresentation(url, false, (UInt8 *)strBuffer, STRBUF_LEN)) {
516            if (strBuffer[0] == '.' && strBuffer[1] == '/') {
517                // remove the leading "./"
518                char *fromBufPtr = strBuffer + 2;
519                while (true) {
520                    *strBuffer = *fromBufPtr;
521                    if (*fromBufPtr == '\0') break;
522                    strBuffer++;
523                    fromBufPtr++;
524                }
525            }
526        } else {
527            strcpy(strBuffer, "[can't get path: CFURLGetFileSystemRepresentation failed]");
528        }
529    }
530    return isRemote;
531}
532
533void printPathFromURL(CFURLRef url, FILE *stream) {
534    static char strBuffer[STRBUF_LEN];
535    check(url != NULL && stream != NULL);
536    stringFromURLIsRemote(url, strBuffer);
537    fprintf(stream, "%s\n", strBuffer);
538}
539
540void printDateTime(const char *label, UTCDateTime *utcTime, const char *postLabel, Boolean printIfEmpty) {
541    static CFDateFormatterRef formatter = NULL;
542    static char strBuffer[STRBUF_LEN];
543    if (formatter == NULL) {
544        formatter = CFDateFormatterCreate(NULL, CFLocaleCopyCurrent(), kCFDateFormatterShortStyle, kCFDateFormatterMediumStyle);
545    }
546    CFAbsoluteTime absoluteTime;
547    OSStatus err;
548
549    err = UCConvertUTCDateTimeToCFAbsoluteTime(utcTime, &absoluteTime);
550    if (err == kUTCUnderflowErr) {
551        if (printIfEmpty) printf("\t%s: (not set)\n", label);
552        return;
553    }
554    if (err != noErr) osstatusexit(err, "unable to convert UTC %s time", label);
555
556    CFStringRef dateTimeString = CFDateFormatterCreateStringWithAbsoluteTime(NULL, formatter, absoluteTime);
557    CFStringGetCString(dateTimeString, strBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
558   
559    printf("\t%s: %s%s\n", label, strBuffer, postLabel);
560}
561
562#define DFORMAT(SIZE) ((float)(SIZE) / 1024.)
563
564void printSizes(const char *label, UInt64 logicalSize, UInt64 physicalSize, Boolean printIfZero) {
565    UInt32 bigSize = physicalSize >> 32, littleSize = physicalSize;
566    if (!printIfZero && bigSize == 0 && littleSize == 0) return;
567    printf("\t%s: ", label);
568    if (bigSize == 0) {
569        if (littleSize == 0) {
570            printf("zero bytes on disk (zero bytes used)\n"); return;
571        } else if (littleSize < 1024) printf("%lu bytes", littleSize);
572        else {
573            UInt32 adjSize = littleSize >> 10;
574            if (adjSize < 1024) printf("%.1f KB", DFORMAT(littleSize));
575            else {
576                adjSize >>= 10; littleSize >>= 10;
577                if (adjSize < 1024) printf("%.2f MB", DFORMAT(littleSize));
578                else {
579                    adjSize >>= 10; littleSize >>= 10;
580                    printf("%.2f GB", DFORMAT(littleSize));
581                }
582            }
583        }
584    } else {
585        if (bigSize < 256) printf("%lu GB", bigSize);
586        else {
587            bigSize >>= 2;
588            printf("%lu TB", bigSize);
589        }
590    }
591    printf(" on disk (%llu bytes used)\n", logicalSize);
592       
593}
594
595void printMoreInfoForRef(FSRef fsr) {
596    OSStatus err;
597    FSCatalogInfo fscInfo;
598
599    err = FSGetCatalogInfo(&fsr, kFSCatInfoNodeFlags | kFSCatInfoAllDates | kFSCatInfoDataSizes | kFSCatInfoRsrcSizes | kFSCatInfoValence, &fscInfo, NULL, NULL, NULL);
600    if (err != noErr) osstatusexit(err, "unable to get catalog information for file");
601
602    if (fscInfo.nodeFlags & kFSNodeIsDirectoryMask) {
603        printf("\tcontents: ");
604        switch (fscInfo.valence) {
605        case 0: printf("zero items\n"); break;
606        case 1: printf("1 item\n"); break;
607        default: printf("%lu items\n", fscInfo.valence);
608        }
609    } else {
610        printSizes("data fork size", fscInfo.dataLogicalSize, fscInfo.dataPhysicalSize, true);
611        printSizes("rsrc fork size", fscInfo.rsrcLogicalSize, fscInfo.rsrcPhysicalSize, false);
612    }
613
614    if (fscInfo.nodeFlags & (kFSNodeLockedMask | kFSNodeForkOpenMask)) {
615        printf("\tstatus:");
616        if (fscInfo.nodeFlags & kFSNodeLockedMask) {
617            if (fscInfo.nodeFlags & kFSNodeForkOpenMask) printf(" in use,");
618            printf(" locked");
619        } else {
620            printf(" in use");
621        }
622        printf("\n");
623    }
624   
625    printDateTime("created", &fscInfo.createDate, "", true);
626    printDateTime("modified", &fscInfo.contentModDate, "", true);
627    printDateTime("accessed", &fscInfo.accessDate, " [only updated by Mac OS X]", false);
628    printDateTime("backed up", &fscInfo.backupDate, "", false);
629}
630
631const char *utf8StringFromCFStringRef(CFStringRef cfStr) {
632    static char tmpBuffer[STRBUF_LEN];
633    CFStringGetCString(cfStr, tmpBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
634    return tmpBuffer;
635}
636
637const char *utf8StringFromOSType(OSType osType) {
638    CFStringRef typeStr = CFStringCreateWithBytes(NULL, (UInt8 *)&osType, 4, CFStringGetSystemEncoding(), false);
639    if (typeStr == NULL) {
640        // punt to displaying verbatim
641        static char tmpBuffer[4];
642        tmpBuffer[4] = '\0';
643        strncpy(tmpBuffer, (const char *)&osType, 4);
644        return tmpBuffer;
645    }
646    const char *buffer = utf8StringFromCFStringRef(typeStr);
647    CFRelease(typeStr);
648    return buffer;
649}
650
651// 'context' is to match prototype for CFArrayApplierFunction, it's unused
652void printInfoFromURL(CFURLRef url, void *context) {
653    CFStringRef kind;
654    static char strBuffer[STRBUF_LEN];
655   
656    check(url != NULL && context == NULL);
657
658    if (stringFromURLIsRemote(url, strBuffer))
659        printf("<%s>: URL\n", strBuffer);
660    else {
661        static LSItemInfoRecord info;
662        CFStringRef version = NULL;
663        UInt32 intVersion = 0;
664        OSStatus err = LSCopyItemInfoForURL(url, kLSRequestAllInfo, &info);
665        Boolean haveFSRef;
666        FSRef fsr;
667        if (err != noErr) osstatusexit(err, "unable to get information about '%s'", strBuffer);
668        haveFSRef = CFURLGetFSRef(url, &fsr);
669       
670        printf("%s: ", strBuffer);
671       
672        // modifiers
673        if (info.flags & kLSItemInfoIsInvisible) printf("invisible ");
674        if (info.flags & kLSItemInfoAppIsScriptable) printf("scriptable ");
675        if (info.flags & kLSItemInfoIsNativeApp) printf("Mac OS X ");
676        if (info.flags & kLSItemInfoIsClassicApp) printf("Classic ");
677       
678        // kind
679        if (info.flags & kLSItemInfoIsVolume) printf("volume");
680        else if (info.flags & kLSItemInfoIsApplication) printf("application ");
681        else if (info.flags & kLSItemInfoIsPackage) printf("non-application ");
682        else if (info.flags & kLSItemInfoIsContainer) printf("folder");
683        else if (info.flags & kLSItemInfoIsAliasFile) printf("alias");
684        else if (info.flags & kLSItemInfoIsSymlink) printf("symbolic link");
685        else if (info.flags & kLSItemInfoIsPlainFile) printf("document");
686        else printf("unknown file system entity");
687
688        if (info.flags & kLSItemInfoIsPackage) printf("package ");
689
690        if (info.flags & kLSItemInfoAppPrefersNative) printf("[Carbon, prefers native OS X]");
691        else if (info.flags & kLSItemInfoAppPrefersClassic) printf("[Carbon, prefers Classic]");
692
693        printf("\n");
694        if (!(info.flags & kLSItemInfoIsContainer) || info.flags & kLSItemInfoIsPackage) {
695            printf("\ttype: '%s'", utf8StringFromOSType(info.filetype));
696            printf("\tcreator: '%s'\n", utf8StringFromOSType(info.creator));
697        }
698        if (info.flags & kLSItemInfoIsPackage || info.flags & kLSItemInfoIsApplication) {
699                // a package, or possibly a native app with a 'plst' resource
700            CFBundleRef bundle = CFBundleCreate(NULL, url);
701            CFStringRef bundleID = NULL;
702            if (bundle == NULL && (info.flags & kLSItemInfoIsApplication)) {
703                if (info.flags & kLSItemInfoIsPackage || !haveFSRef) {
704                    printf("\t[can't access CFBundle for application]\n");
705                } else { // OS X bug causes this to fail when it shouldn't, so fake it
706                    SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
707                    OSStatus err = ResError();
708                    if (err != noErr) {
709                        printf("\t[can't open resource fork: %s]\n", osstatusstr(err));
710                    } else {
711                        Handle h = Get1Resource('plst', 0);
712                        if ( (err = ResError()) != noErr || h == NULL) {
713                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'plst' 0 resource");
714                        } else {
715                            CFDataRef plstData = CFDataCreate(NULL, (UInt8 *)*h, GetHandleSize(h));
716                            CFStringRef error;
717                            CFPropertyListRef infoPlist = CFPropertyListCreateFromXMLData(NULL, plstData, kCFPropertyListImmutable, &error);
718                            if (plstData != NULL) {
719                                CFRelease(plstData);
720                                plstData = NULL;
721                            } else {
722                                // this function should handle the 'plst' 0 case too, but it doesn't provide error messages; however, it handles the case of an unbundled Mach-O binary, so it is useful as a fallback
723                                infoPlist = CFBundleCopyInfoDictionaryForURL(url);
724                            }
725                            if (infoPlist == NULL) {
726                                printf("\t['plst' 0 resource invalid: %s]\n", utf8StringFromCFStringRef(error));
727                                CFRelease(error);
728                            } else {
729                                // mimic CFBundle logic below
730                                bundleID = CFDictionaryGetValue(infoPlist, kCFBundleIdentifierKey);
731                                if (bundleID != NULL) CFRetain(bundleID);
732                                version = CFDictionaryGetValue(infoPlist, CFSTR("CFBundleShortVersionString"));
733                                if (version == NULL)
734                                    version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey);
735                                if (version != NULL) CFRetain(version);
736                                CFRelease(infoPlist);
737                            }
738                        }
739                        VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
740                        if ( (err = ResError()) != noErr || vers == NULL) {
741                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'vers' 1 resource");
742                        } else {
743                            if (version == NULL) { // prefer 'plst' version
744                                version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
745                            }
746                            intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
747                        }
748                        CloseResFile(resFork);
749                    }
750                }
751            } else {
752                bundleID = CFBundleGetIdentifier(bundle);
753                if (bundleID != NULL) CFRetain(bundleID);
754                // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
755                version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
756                if (version == NULL)
757                    version = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
758                if (version != NULL) {
759                    CFRetain(version);
760                    intVersion = CFBundleGetVersionNumber(bundle);
761                }
762                CFRelease(bundle);
763            }
764            if (bundleID != NULL) {
765                printf("\tbundle ID: %s\n", utf8StringFromCFStringRef(bundleID));
766                CFRelease(bundleID);
767            }
768        } else if (haveFSRef) {
769            // try to get a version if we can, but don't complain if we can't
770            SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
771            if (ResError() == noErr) {
772                VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
773                if (ResError() == noErr && vers != NULL) {
774                    version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
775                    intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
776                }
777            }
778            CloseResFile(resFork);
779        }
780       
781        if (version != NULL) {
782            printf("\tversion: %s", utf8StringFromCFStringRef(version));
783            if (intVersion != 0) printf(" [0x%lx = %lu]", intVersion, intVersion);
784            putchar('\n');
785            CFRelease(version);
786        }
787
788        // kind string
789        err = LSCopyKindStringForURL(url, &kind);
790        if (err != noErr) osstatusexit(err, "unable to get kind of '%s'", strBuffer);
791        printf("\tkind: %s\n", utf8StringFromCFStringRef(kind));
792        CFRelease(kind);
793       
794        if (haveFSRef) {
795            // content type identifier (UTI)
796            err = LSCopyItemAttribute(&fsr, kLSRolesAll, kLSItemContentType, (CFTypeRef *)&kind);
797            if (err == noErr) {
798                printf("\tcontent type ID: %s\n", utf8StringFromCFStringRef(kind));
799                CFRelease(kind);
800            }
801            printMoreInfoForRef(fsr);
802        }
803    }
804}
805
806
807void launchURL(CFURLRef url, ICInstance icInst) {
808    CFStringRef urlStr = CFURLGetString(url);
809    static char strBuffer[STRBUF_LEN];
810    long strStart, strEnd;
811    OSStatus err;
812
813    strBuffer[0] = '\0';
814    CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX no idea what encoding ICLaunchURL is supposed to take; leave as is for now
815    strStart = 0;
816    strEnd = strlen(strBuffer);
817    err = ICLaunchURL(icInst, "\p", strBuffer, strEnd, &strStart, &strEnd);
818    if (err != noErr) {
819        fprintf(stderr, "%s: unable to launch URL <%s>: %s\n", APP_NAME, strBuffer, osstatusstr(err));
820    }
821   
822    CFRelease(urlStr);
823}
824
825OSStatus openItems(void) {
826    if (ITEMS == NULL)
827        ITEMS = CFArrayCreate(NULL, NULL, 0, NULL);
828    // CFShow(LPARAMS.argv);
829    return LSOpenURLsWithRole(ITEMS, kLSRolesAll, NULL, &LPARAMS, NULL, 0);
830}
831
832int main (int argc, char * const argv[]) {
833    OSStatus err;
834   
835    APP_NAME = argv[0];
836    getargs(argc, argv);
837
838    if (OPTS.action == ACTION_FIND || OPTS.action == ACTION_OPEN) {
839        if (LPARAMS.application != NULL) goto findOK; // already have an application FSRef
840        err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, &APPLICATION, NULL);
841        LPARAMS.application = &APPLICATION;
842       
843        if (err != noErr) {
844            if (OPTS.name != NULL && !CFStringHasSuffix(OPTS.name, CFSTR(".app"))) {
845                OPTS.name = CFStringCreateMutableCopy(NULL, CFStringGetLength(OPTS.name) + 4, OPTS.name);
846                CFStringAppend((CFMutableStringRef)OPTS.name, CFSTR(".app"));
847                err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, &APPLICATION, NULL);
848                if (err == noErr) goto findOK;
849            }
850            osstatusexit(err, "can't locate application");
851        }
852        findOK: ;
853    }
854   
855    switch (OPTS.action) {
856    case ACTION_FIND:
857        printPathFromURL(CFURLCreateFromFSRef(NULL, LPARAMS.application), stdout);
858        break;
859    case ACTION_OPEN:
860        err = openItems();
861        if (err != noErr) osstatusexit(err, "can't open application");
862        break;
863    case ACTION_FIND_ITEMS:
864        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
865                             (CFArrayApplierFunction) printPathFromURL, stdout);
866        break;
867    case ACTION_OPEN_ITEMS:
868        err = openItems();
869        if (err != noErr) osstatusexit(err, "can't open items");
870        break;
871    case ACTION_INFO_ITEMS:
872        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
873                             (CFArrayApplierFunction) printInfoFromURL, NULL);
874        break;
875    case ACTION_LAUNCH_URLS:
876    {
877        ICInstance icInst;
878        err = ICStart(&icInst, '\?\?\?\?'); // in case GCC trigraph handling is enabled
879        if (err != noErr) osstatusexit(err, "can't initialize Internet Config", argv[1]);
880        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
881                             (CFArrayApplierFunction) launchURL, icInst);
882        ICStop(icInst);
883        break;
884    }
885    }
886
887    if (TEMPFILE != NULL) {
888        // the application may take a while to finish opening the temporary file
889        daemon(0, 0);
890        sleep(60);
891        unlink(TEMPFILE);
892    }
893
894    return 0;
895}
Note: See TracBrowser for help on using the repository browser.