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

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

launch 1.0b1.

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