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

Last change on this file since 309 was 309, checked in by Nicholas Riley, 13 years ago

VERSION: Updated for 1.1.

main.c: Disabled -o since it doesn't work.

launch.1: Updated for 1.1. Added description of -a which had been
missing (oops).

README: Updated for 1.1. Turned out the Resorcerer example is still
fine (though rather out of date contextually, oh well.)

File size: 39.3 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 BROKEN_ARGUMENTS 1
20#define kComponentSignatureString "launch"
21
22#include <unistd.h>
23#include <sys/stat.h>
24#include <mach-o/fat.h>
25#include <mach-o/arch.h>
26#include <mach-o/loader.h>
27#include <Carbon/Carbon.h>
28#include <CoreServices/CoreServices.h>
29#include <CoreFoundation/CoreFoundation.h>
30#include <ApplicationServices/ApplicationServices.h>
31
32#ifndef BROKEN_AUTHORIZATION
33#include <Security/Authorization.h>
34#include <Security/AuthorizationTags.h>
35#endif
36
37const char *APP_NAME;
38
39#define VERSION "1.1"
40
41#define STRBUF_LEN 1024
42#define ACTION_DEFAULT ACTION_OPEN
43
44struct {
45    OSType creator;
46    CFStringRef bundleID, name;
47    Boolean forceURLs;
48    enum { ACTION_FIND, ACTION_FIND_ITEMS,
49           ACTION_OPEN, ACTION_OPEN_ITEMS, 
50           ACTION_INFO_ITEMS, ACTION_LAUNCH_URLS } action;
51} OPTS = 
52{
53    kLSUnknownCreator, NULL, NULL, false,
54    ACTION_DEFAULT
55};
56
57#define DEFAULT_LAUNCH_FLAGS (kLSLaunchNoParams | kLSLaunchStartClassic | kLSLaunchAsync)
58
59LSApplicationParameters LPARAMS = {0, DEFAULT_LAUNCH_FLAGS, NULL, NULL, NULL, NULL, NULL};
60CFArrayRef ITEMS = NULL;
61FSRef APPLICATION;
62
63char *TEMPFILE = NULL;
64
65typedef struct {
66    OSStatus status;
67    const char *desc;
68} errRec, errList[];
69
70static errList ERRS = {
71    // Launch Services errors
72    { kLSUnknownErr, "unknown Launch Services error" },
73    { kLSApplicationNotFoundErr, "application not found" },
74    { kLSLaunchInProgressErr, "application is being opened; please try again after the application is open" },
75    { kLSNotRegisteredErr, "application not registered in Launch Services database" },
76    { kLSNoExecutableErr, "application package contains no executable, or an unusable executable" },
77    { kLSNoClassicEnvironmentErr, "Classic environment required but not available" },
78    { kLSMultipleSessionsNotSupportedErr, "unable to launch multiple instances of application" },
79#ifndef BROKEN_AUTHORIZATION
80    // Security framework errors
81    { errAuthorizationDenied, "authorization denied" },
82    { errAuthorizationCanceled, "authentication was cancelled" },
83#endif
84    // Internet Config errors
85    { icPrefNotFoundErr, "no helper application is defined for the URL's scheme" },
86    { icNoURLErr, "not a URL" },
87    { icInternalErr, "internal Internet Config error" },
88    // Misc. errors
89    { nsvErr, "the volume cannot be found (buggy filesystem?)" },
90    { procNotFound, "unable to connect to system service.\nAre you logged in?" },
91    { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" },
92    { kCGErrorApplicationRequiresNewerSystem, "application requires a newer Mac OS X version" },
93    { fnfErr, "file not found" },
94    { 0, NULL }
95};
96
97void usage() {
98    fprintf(stderr, "usage: %s [-npswbmhCXLU] [-c creator] [-i bundleID] [-u URL] [-a name|path] [-o argument] [item ...] [-]\n"
99                    "   or: %s [-npflswbmhCXLU] "
100#ifndef BROKEN_ARGUMENTS
101                    "[-o argument] "
102#endif
103                    "item ...\n", APP_NAME, APP_NAME);
104    fprintf(stderr,
105        "  -n            print matching paths/URLs instead of opening them\n"
106        "  -p            ask application(s) to print document(s)\n"
107        "  -f            display information about item(s)\n"
108        "  -l            launch URLs (e.g. treat http:// URLs as Web sites, not WebDAV)\n"
109#ifndef BROKEN_AUTHORIZATION
110        "  -s            launch target(s) as superuser (authenticating if needed)\n"
111#endif
112        "  -w            wait for application to finish opening before exiting\n"
113        "  -b            launch application in the background\n"
114        "  -m            launch application again, even if already running\n"
115        "  -h            hide application once it's finished opening\n"
116        "  -C            force CFM/PEF Carbon application to launch in Classic\n"
117        "  -X            don't start Classic for this app if Classic isn't running\n"
118        "  -L            suppress normal opening behavior (e.g. untitled window)\n"
119        "  -U            interpret items as URLs, even if same-named files exist\n"
120        "  -c creator    match application by four-character creator code ('ToyS')\n"
121        "  -i bundle ID  match application by bundle identifier (com.apple.scripteditor)\n"
122        "  -u URL        open application at file:// URL (NOT RECOMMENDED for scripts)\n"
123        "  -a name|path  match application by name/path (NOT RECOMMENDED, very fragile)\n"
124#ifndef BROKEN_ARGUMENTS
125        "  -o argument   pass argument to application (may be specified more than once)\n"
126#endif
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-06 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((UInt8 *)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    OSStatus err;
340    Boolean appSpecified = false;
341
342    if (argc == 1) usage();
343   
344    while ( (ch = getopt(argc, argv, "npflswbmhCXLUc:i:u:a:o:")) != -1) {
345        switch (ch) {
346        case 'n':
347            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
348            OPTS.action = ACTION_FIND;
349            break;
350        case 'p':
351            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
352            OPTS.action = ACTION_OPEN;
353            LPARAMS.flags |= kLSLaunchAndPrint;
354            break;
355        case 'f':
356            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
357            OPTS.action = ACTION_INFO_ITEMS;
358            break;
359        case 'l':
360            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
361            OPTS.action = ACTION_LAUNCH_URLS;
362            break;
363        case 's':
364#ifdef BROKEN_AUTHORIZATION
365            errexit("-s option no longer functional after 10.1 Security Update, sorry");
366#else
367        {
368            AuthorizationRef authRef;
369            AuthorizationItem item = { kAuthorizationRightExecute, strlen(argv[0]), argv[0], 0 };
370            OSStatus err;
371           
372            if (authenticated(item, &authRef)) {
373                continue;
374            }
375            authenticate(item, authRef);
376            err = AuthorizationExecuteWithPrivileges(authRef, argv[0], 0, &argv[1], NULL);
377            if (err != noErr) osstatusexit(err, "unable to launch '%s' with superuser privileges", argv[0]);
378            exit(0); // XXX exit status propagate?
379        }
380#endif
381        case 'w': LPARAMS.flags ^= kLSLaunchAsync; break;      // synchronous
382        case 'b': LPARAMS.flags |= kLSLaunchDontSwitch; break; // open in background
383        case 'm': LPARAMS.flags |= kLSLaunchNewInstance; break;// open multiple
384        case 'h': LPARAMS.flags |= kLSLaunchAndHide; break;    // hide once launched
385        case 'C': LPARAMS.flags |= kLSLaunchInClassic; break;  // force Classic
386        case 'X': LPARAMS.flags ^= kLSLaunchStartClassic; break;// don't start Classic for app
387        case 'L':
388        {
389            OSStatus err;
390            LPARAMS.initialEvent = malloc(sizeof(AppleEvent));
391            err = AECreateAppleEvent(kASAppleScriptSuite, kASLaunchEvent, NULL, kAutoGenerateReturnID, kAnyTransactionID, LPARAMS.initialEvent);
392            if (err != noErr) osstatusexit(err, "unable to construct launch Apple Event", argv[0]);
393        }
394        case 'U': OPTS.forceURLs = true; break;
395        case 'c':
396            if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long");
397            OPTS.creator = htonl(*(OSTypePtr)optarg);
398            appSpecified = true;
399            break;
400        case 'i':
401            OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
402            appSpecified = true;
403            break;
404        case 'a':
405            err = FSPathMakeRef((UInt8 *)optarg, &APPLICATION, NULL);
406            if (err == noErr) {
407                LPARAMS.application = &APPLICATION;
408            } else {
409                OPTS.name = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
410            }
411            appSpecified = true;
412            break;
413        case 'u':
414            { CFStringRef str = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
415              CFURLRef appURL = CFURLCreateWithString(NULL, str, NULL);
416              if (appURL == NULL)
417                  errexit("invalid URL (argument of -u)");
418              err = CFURLGetFSRef(appURL, &APPLICATION);
419              if (err != noErr)
420                  osstatusexit(err, "can't find application (argument of -u)");
421            }
422            LPARAMS.application = &APPLICATION;
423            appSpecified = true;
424            break;
425#ifndef BROKEN_ARGUMENTS
426        case 'o':
427            if (LPARAMS.argv == NULL)
428                LPARAMS.argv = CFArrayCreateMutable(NULL, 0, NULL);
429            CFArrayAppendValue((CFMutableArrayRef)LPARAMS.argv,
430                               CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8));
431            break;
432#endif
433        default: usage();
434        }
435    }
436   
437    argc -= optind;
438    argv += optind;
439   
440    if ( (OPTS.action == ACTION_FIND || OPTS.action == ACTION_LAUNCH_URLS ||
441          OPTS.action == ACTION_INFO_ITEMS) && LPARAMS.flags != DEFAULT_LAUNCH_FLAGS)
442        errexit("options -s, -b, -m, -h, -C, -X apply to application launch (not -n, -f or -l)");
443   
444    if (OPTS.creator == kLSUnknownCreator && OPTS.bundleID == NULL && OPTS.name == NULL) {
445        if (argc == 0 && LPARAMS.application == NULL)
446            errexit("must specify an application by -u, or one or more of -c, -i, -a");
447        if (!appSpecified) {
448            if (OPTS.action == ACTION_FIND)
449                OPTS.action = ACTION_FIND_ITEMS;
450            if (OPTS.action == ACTION_OPEN)
451                OPTS.action = ACTION_OPEN_ITEMS;
452        }
453    } else {
454        if (LPARAMS.application != NULL)
455            errexit("application URL (argument of -u) incompatible with matching by -c, -i, -a");
456    }
457
458    if (OPTS.action == ACTION_LAUNCH_URLS && appSpecified)
459        errexit("launching URLs with a given application is not supported; try without -l");
460
461    if (OPTS.action == ACTION_INFO_ITEMS && appSpecified)
462        errexit("can't get information (-f) on item(s) using an application (-u, -c, -i, -a)");
463
464    if (argc == 0 && OPTS.action == ACTION_OPEN && LPARAMS.flags & kLSLaunchAndPrint)
465        errexit("print option (-p) must be accompanied by document(s) to print");
466   
467    if (argc != 0) {
468        int i;
469        OSStatus err;
470        CFStringRef argStr;
471        CFURLRef itemURL;
472        LSItemInfoRecord docInfo;
473
474        if (OPTS.action == ACTION_FIND)
475            errexit("application with documents only supported for open or print, not find");
476
477        // handle document/item/URL arguments
478        ITEMS = CFArrayCreateMutable(NULL, argc, NULL);
479        for (i = 0 ; i < argc ; i++) {
480            argStr = NULL;
481            if (strcmp(argv[i], "-") == 0) {
482                TEMPFILE = stdinAsTempFile();
483                itemURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)TEMPFILE, strlen(TEMPFILE), false);
484                LPARAMS.flags ^= kLSLaunchAsync;
485            } else {
486                struct stat stat_buf;
487                if (!OPTS.forceURLs && stat(argv[i], &stat_buf) == 0) {
488                    itemURL = NULL;
489                } else {
490                    argStr = CFStringCreateWithCString(NULL, argv[i], kCFStringEncodingUTF8);
491                    // check for URLs
492                    itemURL = normalizedURLFromString(argStr);
493                    if (itemURL == NULL && OPTS.action == ACTION_LAUNCH_URLS) {
494                        // check for email addresses
495                        if (strchr(argv[i], '@') != NULL && strchr(argv[i], '/') == NULL)
496                            itemURL = normalizedURLFromPrefixSlack(CFSTR("mailto:"), argStr);
497                        // check for "slack" URLs
498                        if (itemURL == NULL && strchr(argv[i], '.') != NULL && strchr(argv[i], '/') != argv[i])
499                            itemURL = normalizedURLFromPrefixSlack(CFSTR("http://"), argStr);
500                    }
501                }
502                if (itemURL == NULL) {
503                    // check for file paths
504                    itemURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)argv[i], strlen(argv[i]), false);
505                    err = LSCopyItemInfoForURL(itemURL, kLSRequestExtensionFlagsOnly, &docInfo);
506                    if (err != noErr) osstatusexit(err, "unable to locate '%s'", argv[i]);
507                }
508            }
509            CFArrayAppendValue((CFMutableArrayRef)ITEMS, itemURL);
510            // don't CFRelease the itemURL because CFArray doesn't retain it by default
511            if (argStr != NULL) CFRelease(argStr);
512        }
513    }
514}
515
516Boolean stringFromURLIsRemote(CFURLRef url, char *strBuffer) {
517    CFStringRef scheme = CFURLCopyScheme(url);
518    Boolean isRemote = !CFEqual(scheme, CFSTR("file"));
519    CFRelease(scheme);
520   
521    strBuffer[0] = '\0';
522    if (isRemote) {
523        CFStringRef urlString = CFURLGetString(url);
524        CFStringGetCString(urlString, strBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
525        CFRelease(urlString);
526    } else {
527        if (CFURLGetFileSystemRepresentation(url, false, (UInt8 *)strBuffer, STRBUF_LEN)) {
528            if (strBuffer[0] == '.' && strBuffer[1] == '/') {
529                // remove the leading "./"
530                char *fromBufPtr = strBuffer + 2;
531                while (true) {
532                    *strBuffer = *fromBufPtr;
533                    if (*fromBufPtr == '\0') break;
534                    strBuffer++;
535                    fromBufPtr++;
536                }
537            }
538        } else {
539            strcpy(strBuffer, "[can't get path: CFURLGetFileSystemRepresentation failed]");
540        }
541    }
542    return isRemote;
543}
544
545void printPathFromURL(CFURLRef url, FILE *stream) {
546    static char strBuffer[STRBUF_LEN];
547    check(url != NULL && stream != NULL);
548    stringFromURLIsRemote(url, strBuffer);
549    fprintf(stream, "%s\n", strBuffer);
550}
551
552void printDateTime(const char *label, UTCDateTime *utcTime, const char *postLabel, Boolean printIfEmpty) {
553    static CFDateFormatterRef formatter = NULL;
554    static char strBuffer[STRBUF_LEN];
555    if (formatter == NULL) {
556        formatter = CFDateFormatterCreate(NULL, CFLocaleCopyCurrent(), kCFDateFormatterShortStyle, kCFDateFormatterMediumStyle);
557    }
558    CFAbsoluteTime absoluteTime;
559    OSStatus err;
560
561    err = UCConvertUTCDateTimeToCFAbsoluteTime(utcTime, &absoluteTime);
562    if (err == kUTCUnderflowErr) {
563        if (printIfEmpty) printf("\t%s: (not set)\n", label);
564        return;
565    }
566    if (err != noErr) osstatusexit(err, "unable to convert UTC %s time", label);
567
568    CFStringRef dateTimeString = CFDateFormatterCreateStringWithAbsoluteTime(NULL, formatter, absoluteTime);
569    CFStringGetCString(dateTimeString, strBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
570   
571    printf("\t%s: %s%s\n", label, strBuffer, postLabel);
572}
573
574#define DFORMAT(SIZE) ((float)(SIZE) / 1024.)
575
576void printSizes(const char *label, UInt64 logicalSize, UInt64 physicalSize, Boolean printIfZero) {
577    UInt32 bigSize = physicalSize >> 32, littleSize = physicalSize;
578    if (!printIfZero && bigSize == 0 && littleSize == 0) return;
579    printf("\t%s: ", label);
580    if (bigSize == 0) {
581        if (littleSize == 0) {
582            printf("zero bytes on disk (zero bytes used)\n"); return;
583        } else if (littleSize < 1024) printf("%lu bytes", littleSize);
584        else {
585            UInt32 adjSize = littleSize >> 10;
586            if (adjSize < 1024) printf("%.1f KB", DFORMAT(littleSize));
587            else {
588                adjSize >>= 10; littleSize >>= 10;
589                if (adjSize < 1024) printf("%.2f MB", DFORMAT(littleSize));
590                else {
591                    adjSize >>= 10; littleSize >>= 10;
592                    printf("%.2f GB", DFORMAT(littleSize));
593                }
594            }
595        }
596    } else {
597        if (bigSize < 256) printf("%lu GB", bigSize);
598        else {
599            bigSize >>= 2;
600            printf("%lu TB", bigSize);
601        }
602    }
603    printf(" on disk (%llu bytes used)\n", logicalSize);
604       
605}
606
607void printMoreInfoForRef(FSRef fsr) {
608    OSStatus err;
609    FSCatalogInfo fscInfo;
610
611    err = FSGetCatalogInfo(&fsr, kFSCatInfoNodeFlags | kFSCatInfoAllDates | kFSCatInfoDataSizes | kFSCatInfoRsrcSizes | kFSCatInfoValence, &fscInfo, NULL, NULL, NULL);
612    if (err != noErr) osstatusexit(err, "unable to get catalog information for file");
613
614    if (fscInfo.nodeFlags & kFSNodeIsDirectoryMask) {
615        printf("\tcontents: ");
616        switch (fscInfo.valence) {
617        case 0: printf("zero items\n"); break;
618        case 1: printf("1 item\n"); break;
619        default: printf("%lu items\n", fscInfo.valence);
620        }
621    } else {
622        printSizes("data fork size", fscInfo.dataLogicalSize, fscInfo.dataPhysicalSize, true);
623        printSizes("rsrc fork size", fscInfo.rsrcLogicalSize, fscInfo.rsrcPhysicalSize, false);
624    }
625
626    if (fscInfo.nodeFlags & (kFSNodeLockedMask | kFSNodeForkOpenMask)) {
627        printf("\tstatus:");
628        if (fscInfo.nodeFlags & kFSNodeLockedMask) {
629            if (fscInfo.nodeFlags & kFSNodeForkOpenMask) printf(" in use,");
630            printf(" locked");
631        } else {
632            printf(" in use");
633        }
634        printf("\n");
635    }
636   
637    printDateTime("created", &fscInfo.createDate, "", true);
638    printDateTime("modified", &fscInfo.contentModDate, "", true);
639    printDateTime("accessed", &fscInfo.accessDate, " [only updated by Mac OS X]", false);
640    printDateTime("backed up", &fscInfo.backupDate, "", false);
641}
642
643const char *utf8StringFromCFStringRef(CFStringRef cfStr) {
644    static char tmpBuffer[STRBUF_LEN];
645    CFStringGetCString(cfStr, tmpBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
646    return tmpBuffer;
647}
648
649const char *utf8StringFromOSType(OSType osType) {
650    osType = ntohl(osType);
651    CFStringRef typeStr = CFStringCreateWithBytes(NULL, (UInt8 *)&osType, 4, CFStringGetSystemEncoding(), false);
652    if (typeStr == NULL) {
653        // punt to displaying verbatim
654        static char tmpBuffer[4];
655        tmpBuffer[4] = '\0';
656        strncpy(tmpBuffer, (const char *)&osType, 4);
657        return tmpBuffer;
658    }
659    const char *buffer = utf8StringFromCFStringRef(typeStr);
660    CFRelease(typeStr);
661    return buffer;
662}
663
664// based on Apple's "CheckExecutableArchitecture" sample code
665
666#define MAX_HEADER_BYTES 512
667
668void swapHeader(uint8_t *bytes, ssize_t length) {
669    for (ssize_t i = 0 ; i < length ; i += 4)
670        *(uint32_t *)(bytes + i) = OSSwapInt32(*(uint32_t *)(bytes + i));
671}
672
673void printExecutableArchitectures(CFURLRef url, bool printOnFailure) {
674    uint8_t path[PATH_MAX];
675    if (printOnFailure)
676        printf("\tarchitecture: ");
677       
678    if (!CFURLGetFileSystemRepresentation(url, true, path, PATH_MAX)) {
679        if (printOnFailure) printf("(can't get executable)\n");
680        return;
681    }
682   
683    int fd = open((const char *)path, O_RDONLY, 0777);
684    if (fd <= 0) {
685        if (printOnFailure) printf("(can't read)\n");
686        return;
687    }
688   
689    uint8_t bytes[MAX_HEADER_BYTES];
690    ssize_t length = read(fd, bytes, MAX_HEADER_BYTES);
691    close(fd);
692
693    if (length < sizeof(struct mach_header_64)) {
694        if (printOnFailure) printf("(can't read Mach-O header)\n");
695        return;
696    }
697
698    // Look for any of the six magic numbers relevant to Mach-O executables, and swap the header if necessary.
699    uint32_t num_fat = 0, magic = *((uint32_t *)bytes);
700    uint32_t max_fat = (length - sizeof(struct fat_header)) / sizeof(struct fat_arch);
701    struct fat_arch one_fat = {0}, *fat;
702    if (MH_MAGIC == magic || MH_CIGAM == magic) {
703        struct mach_header *mh = (struct mach_header *)bytes;
704        if (MH_CIGAM == magic) swapHeader(bytes, length);
705        one_fat.cputype = mh->cputype;
706        one_fat.cpusubtype = mh->cpusubtype;
707        fat = &one_fat;
708        num_fat = 1;
709    } else if (MH_MAGIC_64 == magic || MH_CIGAM_64 == magic) {
710        struct mach_header_64 *mh = (struct mach_header_64 *)bytes;
711        if (MH_CIGAM_64 == magic) swapHeader(bytes, length);
712        one_fat.cputype = mh->cputype;
713        one_fat.cpusubtype = mh->cpusubtype;
714        fat = &one_fat;
715        num_fat = 1;
716    } else if (FAT_MAGIC == magic || FAT_CIGAM == magic) {
717        fat = (struct fat_arch *)(bytes + sizeof(struct fat_header));
718        if (FAT_CIGAM == magic) swapHeader(bytes, length);
719        num_fat = ((struct fat_header *)bytes)->nfat_arch;
720        if (num_fat > max_fat) num_fat = max_fat;
721    }
722   
723    if (num_fat == 0) {
724        if (printOnFailure) printf("(none found)\n");
725        return;
726    }
727   
728    if (!printOnFailure)
729        printf("\tarchitecture: ");
730       
731    for (int i = 0 ; i < num_fat ; i++) {
732        if (i != 0) printf(", ");
733        const NXArchInfo *arch = NXGetArchInfoFromCpuType(fat[i].cputype, fat[i].cpusubtype);
734        if (arch == NULL) {
735            printf("unknown (cputype %d, subtype %d)", fat[i].cputype, fat[i].cpusubtype);
736            continue;
737        }
738        printf(arch->description);
739    }
740    printf("\n");
741}
742
743// 'context' is to match prototype for CFArrayApplierFunction, it's unused
744void printInfoFromURL(CFURLRef url, void *context) {
745    CFStringRef kind;
746    static char strBuffer[STRBUF_LEN];
747   
748    check(url != NULL && context == NULL);
749
750    if (stringFromURLIsRemote(url, strBuffer))
751        printf("<%s>: URL\n", strBuffer);
752    else {
753        static LSItemInfoRecord info;
754        CFStringRef version = NULL;
755        UInt32 intVersion = 0;
756        OSStatus err = LSCopyItemInfoForURL(url, kLSRequestAllInfo, &info);
757        Boolean haveFSRef;
758        FSRef fsr;
759        if (err != noErr) osstatusexit(err, "unable to get information about '%s'", strBuffer);
760        haveFSRef = CFURLGetFSRef(url, &fsr);
761       
762        printf("%s: ", strBuffer);
763       
764        // modifiers
765        if (info.flags & kLSItemInfoIsInvisible) printf("invisible ");
766        if (info.flags & kLSItemInfoAppIsScriptable) printf("scriptable ");
767        if (info.flags & kLSItemInfoIsNativeApp) printf("Mac OS X ");
768        if (info.flags & kLSItemInfoIsClassicApp) printf("Classic ");
769       
770        // kind
771        if (info.flags & kLSItemInfoIsVolume) printf("volume");
772        else if (info.flags & kLSItemInfoIsApplication) printf("application ");
773        else if (info.flags & kLSItemInfoIsPackage) printf("non-application ");
774        else if (info.flags & kLSItemInfoIsContainer) printf("folder");
775        else if (info.flags & kLSItemInfoIsAliasFile) printf("alias");
776        else if (info.flags & kLSItemInfoIsSymlink) printf("symbolic link");
777        else if (info.flags & kLSItemInfoIsPlainFile) printf("document");
778        else printf("unknown file system entity");
779
780        if (info.flags & kLSItemInfoIsPackage) printf("package ");
781
782        if (info.flags & kLSItemInfoAppPrefersNative) printf("[Carbon, prefers native OS X]");
783        else if (info.flags & kLSItemInfoAppPrefersClassic) printf("[Carbon, prefers Classic]");
784
785        printf("\n");
786        if (!(info.flags & kLSItemInfoIsContainer) || info.flags & kLSItemInfoIsPackage) {
787            printf("\ttype: '%s'", utf8StringFromOSType(info.filetype));
788            printf("\tcreator: '%s'\n", utf8StringFromOSType(info.creator));
789        }
790        if (info.flags & kLSItemInfoIsPackage || info.flags & kLSItemInfoIsApplication) {
791                // a package, or possibly a native app with a 'plst' resource
792            CFBundleRef bundle = CFBundleCreate(NULL, url);
793            CFStringRef bundleID = NULL;
794            if (bundle == NULL && (info.flags & kLSItemInfoIsApplication)) {
795                if (info.flags & kLSItemInfoIsPackage || !haveFSRef) {
796                    printf("\t[can't access CFBundle for application]\n");
797                } else { // OS X bug causes this to fail when it shouldn't, so fake it
798                    SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
799                    OSStatus err = ResError();
800                    if (err != noErr) {
801                        printf("\t[can't open resource fork: %s]\n", osstatusstr(err));
802                    } else {
803                        Handle h = Get1Resource('plst', 0);
804                        if ( (err = ResError()) != noErr || h == NULL) {
805                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'plst' 0 resource");
806                        } else {
807                            CFDataRef plstData = CFDataCreate(NULL, (UInt8 *)*h, GetHandleSize(h));
808                            CFStringRef error;
809                            CFPropertyListRef infoPlist = CFPropertyListCreateFromXMLData(NULL, plstData, kCFPropertyListImmutable, &error);
810                            if (plstData != NULL) {
811                                CFRelease(plstData);
812                                plstData = NULL;
813                            } else {
814                                // 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
815                                infoPlist = CFBundleCopyInfoDictionaryForURL(url);
816                            }
817                            if (infoPlist == NULL) {
818                                printf("\t['plst' 0 resource invalid: %s]\n", utf8StringFromCFStringRef(error));
819                                CFRelease(error);
820                            } else {
821                                // mimic CFBundle logic below
822                                bundleID = CFDictionaryGetValue(infoPlist, kCFBundleIdentifierKey);
823                                if (bundleID != NULL) CFRetain(bundleID);
824                                version = CFDictionaryGetValue(infoPlist, CFSTR("CFBundleShortVersionString"));
825                                if (version == NULL)
826                                    version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey);
827                                if (version != NULL) CFRetain(version);
828                                CFRelease(infoPlist);
829                            }
830                        }
831                        VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
832                        if ( (err = ResError()) != noErr || vers == NULL) {
833                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'vers' 1 resource");
834                        } else {
835                            if (version == NULL) { // prefer 'plst' version
836                                version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
837                            }
838                            intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
839                        }
840                        CloseResFile(resFork);
841                    }
842                }
843            } else {
844                bundleID = CFBundleGetIdentifier(bundle);
845                if (bundleID != NULL) CFRetain(bundleID);
846                // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
847                version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
848                if (version == NULL)
849                    version = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
850                if (version != NULL) {
851                    CFRetain(version);
852                    intVersion = CFBundleGetVersionNumber(bundle);
853                }
854                CFURLRef executable = CFBundleCopyExecutableURL(bundle);
855                if (executable != NULL) {
856                    printExecutableArchitectures(executable, true);
857                    CFRelease(executable);
858                }
859                CFRelease(bundle);
860            }
861            if (bundleID != NULL) {
862                printf("\tbundle ID: %s\n", utf8StringFromCFStringRef(bundleID));
863                CFRelease(bundleID);
864            }
865        } else {
866            printExecutableArchitectures(url, false);
867            if (haveFSRef) {
868                // try to get a version if we can, but don't complain if we can't
869                SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
870                if (ResError() == noErr) {
871                    VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
872                    if (ResError() == noErr && vers != NULL) {
873                        version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
874                        intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
875                    }
876                }
877                CloseResFile(resFork);
878            }
879        }
880
881        if (version != NULL) {
882            printf("\tversion: %s", utf8StringFromCFStringRef(version));
883            if (intVersion != 0) printf(" [0x%lx = %lu]", intVersion, intVersion);
884            putchar('\n');
885            CFRelease(version);
886        }
887
888        // kind string
889        err = LSCopyKindStringForURL(url, &kind);
890        if (err != noErr) osstatusexit(err, "unable to get kind of '%s'", strBuffer);
891        printf("\tkind: %s\n", utf8StringFromCFStringRef(kind));
892        CFRelease(kind);
893       
894        if (haveFSRef) {
895            // content type identifier (UTI)
896            err = LSCopyItemAttribute(&fsr, kLSRolesAll, kLSItemContentType, (CFTypeRef *)&kind);
897            if (err == noErr) {
898                printf("\tcontent type ID: %s\n", utf8StringFromCFStringRef(kind));
899                CFRelease(kind);
900            }
901            printMoreInfoForRef(fsr);
902        }
903    }
904}
905
906
907void launchURL(CFURLRef url, ICInstance icInst) {
908    CFStringRef urlStr = CFURLGetString(url);
909    static char strBuffer[STRBUF_LEN];
910    long strStart, strEnd;
911    OSStatus err;
912
913    strBuffer[0] = '\0';
914    CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX no idea what encoding ICLaunchURL is supposed to take; leave as is for now
915    strStart = 0;
916    strEnd = strlen(strBuffer);
917    err = ICLaunchURL(icInst, "\p", strBuffer, strEnd, &strStart, &strEnd);
918    if (err != noErr) {
919        fprintf(stderr, "%s: unable to launch URL <%s>: %s\n", APP_NAME, strBuffer, osstatusstr(err));
920    }
921   
922    CFRelease(urlStr);
923}
924
925OSStatus openItems(void) {
926    if (ITEMS == NULL)
927        ITEMS = CFArrayCreate(NULL, NULL, 0, NULL);
928    // CFShow(LPARAMS.argv);
929    return LSOpenURLsWithRole(ITEMS, kLSRolesAll, NULL, &LPARAMS, NULL, 0);
930}
931
932int main (int argc, char * const argv[]) {
933    OSStatus err;
934   
935    APP_NAME = argv[0];
936    getargs(argc, argv);
937
938    if (OPTS.action == ACTION_FIND || OPTS.action == ACTION_OPEN) {
939        if (LPARAMS.application != NULL) goto findOK; // already have an application FSRef
940        err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, &APPLICATION, NULL);
941        LPARAMS.application = &APPLICATION;
942       
943        if (err != noErr) {
944            if (OPTS.name != NULL && !CFStringHasSuffix(OPTS.name, CFSTR(".app"))) {
945                OPTS.name = CFStringCreateMutableCopy(NULL, CFStringGetLength(OPTS.name) + 4, OPTS.name);
946                CFStringAppend((CFMutableStringRef)OPTS.name, CFSTR(".app"));
947                err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, &APPLICATION, NULL);
948                if (err == noErr) goto findOK;
949            }
950            osstatusexit(err, "can't locate application");
951        }
952        findOK: ;
953    }
954   
955    switch (OPTS.action) {
956    case ACTION_FIND:
957        printPathFromURL(CFURLCreateFromFSRef(NULL, LPARAMS.application), stdout);
958        break;
959    case ACTION_OPEN:
960        err = openItems();
961        if (err != noErr) osstatusexit(err, "can't open application");
962        break;
963    case ACTION_FIND_ITEMS:
964        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
965                             (CFArrayApplierFunction) printPathFromURL, stdout);
966        break;
967    case ACTION_OPEN_ITEMS:
968        err = openItems();
969        if (err != noErr) osstatusexit(err, "can't open items");
970        break;
971    case ACTION_INFO_ITEMS:
972        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
973                             (CFArrayApplierFunction) printInfoFromURL, NULL);
974        break;
975    case ACTION_LAUNCH_URLS:
976    {
977        ICInstance icInst;
978        err = ICStart(&icInst, '\?\?\?\?'); // in case GCC trigraph handling is enabled
979        if (err != noErr) osstatusexit(err, "can't initialize Internet Config", argv[1]);
980        CFArrayApplyFunction(ITEMS, CFRangeMake(0, CFArrayGetCount(ITEMS)),
981                             (CFArrayApplierFunction) launchURL, icInst);
982        ICStop(icInst);
983        break;
984    }
985    }
986
987    if (TEMPFILE != NULL) {
988        // the application may take a while to finish opening the temporary file
989        daemon(0, 0);
990        sleep(60);
991        unlink(TEMPFILE);
992    }
993
994    return 0;
995}
Note: See TracBrowser for help on using the repository browser.