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

Last change on this file since 651 was 651, checked in by Nicholas Riley, 7 years ago

Make buildable on OS X 10.8 (remove obsolete CoreGraphics? error), 32-bit only.

Fix a tiny buffer overflow.

Don't use printf to print things that aren't format strings.

Bump version number.

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