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

Last change on this file since 148 was 148, checked in by Nicholas Riley, 17 years ago

main.c: replaced error constants with numbers for 10.3+ LaunchServices? errors

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