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

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

main.c: Display architecture with -f.

launch.xcodeproj: Build in C99 mode.

README: Updated a few examples (the Resorcerer one still needs
fixing).

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