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

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

main.c: Fix endianness issues in creator code (thanks, Peter Hosey).

File size: 36.0 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 = htonl(*(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    osType = ntohl(osType);
639    CFStringRef typeStr = CFStringCreateWithBytes(NULL, (UInt8 *)&osType, 4, CFStringGetSystemEncoding(), false);
640    if (typeStr == NULL) {
641        // punt to displaying verbatim
642        static char tmpBuffer[4];
643        tmpBuffer[4] = '\0';
644        strncpy(tmpBuffer, (const char *)&osType, 4);
645        return tmpBuffer;
646    }
647    const char *buffer = utf8StringFromCFStringRef(typeStr);
648    CFRelease(typeStr);
649    return buffer;
650}
651
652// 'context' is to match prototype for CFArrayApplierFunction, it's unused
653void printInfoFromURL(CFURLRef url, void *context) {
654    CFStringRef kind;
655    static char strBuffer[STRBUF_LEN];
656   
657    check(url != NULL && context == NULL);
658
659    if (stringFromURLIsRemote(url, strBuffer))
660        printf("<%s>: URL\n", strBuffer);
661    else {
662        static LSItemInfoRecord info;
663        CFStringRef version = NULL;
664        UInt32 intVersion = 0;
665        OSStatus err = LSCopyItemInfoForURL(url, kLSRequestAllInfo, &info);
666        Boolean haveFSRef;
667        FSRef fsr;
668        if (err != noErr) osstatusexit(err, "unable to get information about '%s'", strBuffer);
669        haveFSRef = CFURLGetFSRef(url, &fsr);
670       
671        printf("%s: ", strBuffer);
672       
673        // modifiers
674        if (info.flags & kLSItemInfoIsInvisible) printf("invisible ");
675        if (info.flags & kLSItemInfoAppIsScriptable) printf("scriptable ");
676        if (info.flags & kLSItemInfoIsNativeApp) printf("Mac OS X ");
677        if (info.flags & kLSItemInfoIsClassicApp) printf("Classic ");
678       
679        // kind
680        if (info.flags & kLSItemInfoIsVolume) printf("volume");
681        else if (info.flags & kLSItemInfoIsApplication) printf("application ");
682        else if (info.flags & kLSItemInfoIsPackage) printf("non-application ");
683        else if (info.flags & kLSItemInfoIsContainer) printf("folder");
684        else if (info.flags & kLSItemInfoIsAliasFile) printf("alias");
685        else if (info.flags & kLSItemInfoIsSymlink) printf("symbolic link");
686        else if (info.flags & kLSItemInfoIsPlainFile) printf("document");
687        else printf("unknown file system entity");
688
689        if (info.flags & kLSItemInfoIsPackage) printf("package ");
690
691        if (info.flags & kLSItemInfoAppPrefersNative) printf("[Carbon, prefers native OS X]");
692        else if (info.flags & kLSItemInfoAppPrefersClassic) printf("[Carbon, prefers Classic]");
693
694        printf("\n");
695        if (!(info.flags & kLSItemInfoIsContainer) || info.flags & kLSItemInfoIsPackage) {
696            printf("\ttype: '%s'", utf8StringFromOSType(info.filetype));
697            printf("\tcreator: '%s'\n", utf8StringFromOSType(info.creator));
698        }
699        if (info.flags & kLSItemInfoIsPackage || info.flags & kLSItemInfoIsApplication) {
700                // a package, or possibly a native app with a 'plst' resource
701            CFBundleRef bundle = CFBundleCreate(NULL, url);
702            CFStringRef bundleID = NULL;
703            if (bundle == NULL && (info.flags & kLSItemInfoIsApplication)) {
704                if (info.flags & kLSItemInfoIsPackage || !haveFSRef) {
705                    printf("\t[can't access CFBundle for application]\n");
706                } else { // OS X bug causes this to fail when it shouldn't, so fake it
707                    SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
708                    OSStatus err = ResError();
709                    if (err != noErr) {
710                        printf("\t[can't open resource fork: %s]\n", osstatusstr(err));
711                    } else {
712                        Handle h = Get1Resource('plst', 0);
713                        if ( (err = ResError()) != noErr || h == NULL) {
714                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'plst' 0 resource");
715                        } else {
716                            CFDataRef plstData = CFDataCreate(NULL, (UInt8 *)*h, GetHandleSize(h));
717                            CFStringRef error;
718                            CFPropertyListRef infoPlist = CFPropertyListCreateFromXMLData(NULL, plstData, kCFPropertyListImmutable, &error);
719                            if (plstData != NULL) {
720                                CFRelease(plstData);
721                                plstData = NULL;
722                            } else {
723                                // 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
724                                infoPlist = CFBundleCopyInfoDictionaryForURL(url);
725                            }
726                            if (infoPlist == NULL) {
727                                printf("\t['plst' 0 resource invalid: %s]\n", utf8StringFromCFStringRef(error));
728                                CFRelease(error);
729                            } else {
730                                // mimic CFBundle logic below
731                                bundleID = CFDictionaryGetValue(infoPlist, kCFBundleIdentifierKey);
732                                if (bundleID != NULL) CFRetain(bundleID);
733                                version = CFDictionaryGetValue(infoPlist, CFSTR("CFBundleShortVersionString"));
734                                if (version == NULL)
735                                    version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey);
736                                if (version != NULL) CFRetain(version);
737                                CFRelease(infoPlist);
738                            }
739                        }
740                        VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
741                        if ( (err = ResError()) != noErr || vers == NULL) {
742                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'vers' 1 resource");
743                        } else {
744                            if (version == NULL) { // prefer 'plst' version
745                                version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
746                            }
747                            intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
748                        }
749                        CloseResFile(resFork);
750                    }
751                }
752            } else {
753                bundleID = CFBundleGetIdentifier(bundle);
754                if (bundleID != NULL) CFRetain(bundleID);
755                // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
756                version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
757                if (version == NULL)
758                    version = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
759                if (version != NULL) {
760                    CFRetain(version);
761                    intVersion = CFBundleGetVersionNumber(bundle);
762                }
763                CFRelease(bundle);
764            }
765            if (bundleID != NULL) {
766                printf("\tbundle ID: %s\n", utf8StringFromCFStringRef(bundleID));
767                CFRelease(bundleID);
768            }
769        } else if (haveFSRef) {
770            // try to get a version if we can, but don't complain if we can't
771            SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
772            if (ResError() == noErr) {
773                VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
774                if (ResError() == noErr && vers != NULL) {
775                    version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
776                    intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
777                }
778            }
779            CloseResFile(resFork);
780        }
781       
782        if (version != NULL) {
783            printf("\tversion: %s", utf8StringFromCFStringRef(version));
784            if (intVersion != 0) printf(" [0x%lx = %lu]", intVersion, intVersion);
785            putchar('\n');
786            CFRelease(version);
787        }
788
789        // kind string
790        err = LSCopyKindStringForURL(url, &kind);
791        if (err != noErr) osstatusexit(err, "unable to get kind of '%s'", strBuffer);
792        printf("\tkind: %s\n", utf8StringFromCFStringRef(kind));
793        CFRelease(kind);
794       
795        if (haveFSRef) {
796            // content type identifier (UTI)
797            err = LSCopyItemAttribute(&fsr, kLSRolesAll, kLSItemContentType, (CFTypeRef *)&kind);
798            if (err == noErr) {
799                printf("\tcontent type ID: %s\n", utf8StringFromCFStringRef(kind));
800                CFRelease(kind);
801            }
802            printMoreInfoForRef(fsr);
803        }
804    }
805}
806
807
808void launchURL(CFURLRef url, ICInstance icInst) {
809    CFStringRef urlStr = CFURLGetString(url);
810    static char strBuffer[STRBUF_LEN];
811    long strStart, strEnd;
812    OSStatus err;
813
814    strBuffer[0] = '\0';
815    CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX no idea what encoding ICLaunchURL is supposed to take; leave as is for now
816    strStart = 0;
817    strEnd = strlen(strBuffer);
818    err = ICLaunchURL(icInst, "\p", strBuffer, strEnd, &strStart, &strEnd);
819    if (err != noErr) {
820        fprintf(stderr, "%s: unable to launch URL <%s>: %s\n", APP_NAME, strBuffer, osstatusstr(err));
821    }
822   
823    CFRelease(urlStr);
824}
825
826OSStatus openItems(void) {
827    if (ITEMS == NULL)
828        ITEMS = CFArrayCreate(NULL, NULL, 0, NULL);
829    // CFShow(LPARAMS.argv);
830    return LSOpenURLsWithRole(ITEMS, kLSRolesAll, NULL, &LPARAMS, NULL, 0);
831}
832
833int main (int argc, char * const argv[]) {
834    OSStatus err;
835   
836    APP_NAME = argv[0];
837    getargs(argc, argv);
838
839    if (OPTS.action == ACTION_FIND || OPTS.action == ACTION_OPEN) {
840        if (LPARAMS.application != NULL) goto findOK; // already have an application FSRef
841        err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, &APPLICATION, NULL);
842        LPARAMS.application = &APPLICATION;
843       
844        if (err != noErr) {
845            if (OPTS.name != NULL && !CFStringHasSuffix(OPTS.name, CFSTR(".app"))) {
846                OPTS.name = CFStringCreateMutableCopy(NULL, CFStringGetLength(OPTS.name) + 4, OPTS.name);
847                CFStringAppend((CFMutableStringRef)OPTS.name, CFSTR(".app"));
848                err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, &APPLICATION, NULL);
849                if (err == noErr) goto findOK;
850            }
851            osstatusexit(err, "can't locate application");
852        }
853        findOK: ;
854    }
855   
856    switch (OPTS.action) {
857    case ACTION_FIND:
858        printPathFromURL(CFURLCreateFromFSRef(NULL, LPARAMS.application), stdout);
859        break;
860    case ACTION_OPEN:
861        err = openItems();
862        if (err != noErr) osstatusexit(err, "can't open application");
863        break;
864    case ACTION_FIND_ITEMS:
865        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
866                             (CFArrayApplierFunction) printPathFromURL, stdout);
867        break;
868    case ACTION_OPEN_ITEMS:
869        err = openItems();
870        if (err != noErr) osstatusexit(err, "can't open items");
871        break;
872    case ACTION_INFO_ITEMS:
873        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
874                             (CFArrayApplierFunction) printInfoFromURL, NULL);
875        break;
876    case ACTION_LAUNCH_URLS:
877    {
878        ICInstance icInst;
879        err = ICStart(&icInst, '\?\?\?\?'); // in case GCC trigraph handling is enabled
880        if (err != noErr) osstatusexit(err, "can't initialize Internet Config", argv[1]);
881        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
882                             (CFArrayApplierFunction) launchURL, icInst);
883        ICStop(icInst);
884        break;
885    }
886    }
887
888    if (TEMPFILE != NULL) {
889        // the application may take a while to finish opening the temporary file
890        daemon(0, 0);
891        sleep(60);
892        unlink(TEMPFILE);
893    }
894
895    return 0;
896}
Note: See TracBrowser for help on using the repository browser.