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

Last change on this file since 137 was 137, checked in by Nicholas Riley, 18 years ago

VERSION: Updated for 1.0b3.

main.c: Work around CFBundle bug to report bundle identifiers and
versions from resource forks of unpackaged Carbon applications and
Classic applications [based on code from Lloyd Dupont]. Updated for
1.0b3.

README: Updated for 1.0b3.

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