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

Last change on this file since 490 was 490, checked in by Nicholas Riley, 10 years ago

display alias targets

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