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

Last change on this file since 166 was 166, checked in by Nicholas Riley, 15 years ago

VERSION: Updated for 1.0.1.

main.c: Updated copyright statement. Updated for 1.0.1. Added -U,
triggers OPTS.forceURLs. Added kLSMultipleSessionsNotSupportedErr,
nsvErr. Cleaned up string encoding handling; works much better now.
Split code into stringFromURLIsRemote, utf8StringFromCFStringRef, and
utf8StringFromOSType. Display "contents: zero items" instead of "0
items" in printMoreInfoForURL. Remove extraneous "./" at beginning of
displayed paths. Get versions of non-{applications, packages} and
info from nonbundled apps with CFBundleCopyInfoDictionaryForURL.
Replaced some error codes with numbers so we support building on 10.2
again.

launch.1: Updated for 1.0.1 and -U option.

README: Updated for 1.0.1. Fixed a paste-o in the uninstallation
instructions.

package-launch.sh: Use zsh explicitly. Build as deployment. Fix
permissions. Make tarball contents owned by root/wheel.

File size: 36.3 KB
Line 
1/*
2 launch - a smarter 'open' replacement
3 Nicholas Riley <launchsw@sabi.net>
4
5 Copyright (c) 2001-05, 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- Launching as root: use authentication framework - doesn't work.
21
22- launch URL with specified URL handler (done, except for IC)
23
24- launch apps by IC protocol handler (esp. editor)
25
26Thanks to:
27
28- Nat Irons, for encouragement and suggestions
29
30- Brian Hill, for the great Security.framework tutorial and sample code
31
32*/
33
34/* #define DEBUG 1 */
35#define BROKEN_AUTHORIZATION 1
36#define BROKEN_LSOPENFROMURLSPEC 1
37#define kComponentSignatureString "launch"
38
39#include <unistd.h>
40#include <sys/stat.h>
41#include <Carbon/Carbon.h>
42#include <CoreServices/CoreServices.h>
43#include <CoreFoundation/CoreFoundation.h>
44#include <ApplicationServices/ApplicationServices.h>
45
46#ifndef BROKEN_AUTHORIZATION
47#include <Security/Authorization.h>
48#include <Security/AuthorizationTags.h>
49#endif
50
51const char *APP_NAME;
52
53#define VERSION "1.0.1"
54
55#define STRBUF_LEN 1024
56#define ACTION_DEFAULT ACTION_OPEN
57
58struct {
59    OSType creator;
60    CFStringRef bundleID, name;
61    Boolean forceURLs;
62    enum { ACTION_FIND, ACTION_FIND_ITEMS,
63           ACTION_OPEN, ACTION_OPEN_ITEMS, 
64           ACTION_INFO_ITEMS, ACTION_LAUNCH_URLS } action;
65} OPTS = 
66{
67    kLSUnknownCreator, NULL, NULL, false,
68    ACTION_DEFAULT
69};
70
71#define DEFAULT_LAUNCH_FLAGS (kLSLaunchNoParams | kLSLaunchStartClassic | kLSLaunchAsync)
72
73LSLaunchURLSpec LSPEC = {NULL, NULL, NULL, DEFAULT_LAUNCH_FLAGS, NULL};
74
75char *TEMPFILE = NULL;
76
77typedef struct {
78    OSStatus status;
79    const char *desc;
80} errRec, errList[];
81
82static errList ERRS = {
83    // Launch Services errors
84    { kLSUnknownErr, "unknown Launch Services error" },
85    { kLSApplicationNotFoundErr, "application not found" },
86    { kLSLaunchInProgressErr, "application is being opened; please try again after the application is open" },
87    { kLSNotRegisteredErr, "application not registered in Launch Services database" },
88    { -10827, "application package contains no executable, or an unusable executable" }, /* kLSNoExecutableErr, not defined in 10.2 */
89    { -10828, "Classic environment required but not available" }, /* kLSNoClassicEnvironmentErr, not defined in 10.2 */
90    { -10829, "unable to launch multiple instances of application" }, /* kLSMultipleSessionsNotSupportedErr, not defined in 10.2 */
91#ifndef BROKEN_AUTHORIZATION
92    // Security framework errors
93    { errAuthorizationDenied, "authorization denied" },
94    { errAuthorizationCanceled, "authentication was cancelled" },
95#endif
96    // Internet Config errors
97    { icPrefNotFoundErr, "no helper application is defined for the URL's scheme" },
98    { icNoURLErr, "not a URL" },
99    { icInternalErr, "internal Internet Config error" },
100    // Misc. errors
101    { nsvErr, "the volume cannot be found (buggy filesystem?)" },
102    { procNotFound, "unable to connect to system service.\nAre you logged in?" },
103    { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" },
104    { kCGErrorApplicationRequiresNewerSystem, "application requires a newer Mac OS X version" },
105    { fnfErr, "file not found" },
106    { 0, NULL }
107};
108
109void usage() {
110    fprintf(stderr, "usage: %s [-npswbmhCXU] [-c creator] [-i bundleID] [-u URL] [-a name] [item ...] [-]\n"
111                    "   or: %s [-npflswbmhCXU] 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        "  -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       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-05 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            } else {
257                CFRelease(scheme);
258            }
259        }
260    }
261    return url;
262}
263
264CFURLRef normalizedURLFromPrefixSlack(CFStringRef prefix, CFStringRef slackStr) {
265    CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"),
266                                               prefix, slackStr);
267    CFURLRef normalizedURL = normalizedURLFromString(str);
268    CFRelease(str);
269    return normalizedURL;
270}
271
272char *tempFile(int *fd) {
273    char *tmpDir = getenv("TMPDIR");
274    const char * const tempTemplate = "/launch-stationery-XXXXXXXX";
275    char *tempPath;
276    OSStatus err;
277    FSRef fsr;
278    FSCatalogInfo catalogInfo;
279    FileInfo *fInfo;
280
281    // create temporary file
282    if (tmpDir == NULL) tmpDir = "/tmp";
283    tempPath = (char *)malloc(strlen(tmpDir) + strlen(tempTemplate) + 1);
284    if (tempPath == NULL) errexit("can't allocate memory");
285    strcpy(tempPath, tmpDir);
286    strcat(tempPath, tempTemplate);
287    if ( (*fd = mkstemp(tempPath)) == -1)
288        errexit("can't create temporary file '%s'", tempPath);
289    // mark file as stationery
290    err = FSPathMakeRef(tempPath, &fsr, NULL);
291    if (err != noErr) osstatusexit(err, "can't find '%s'", tempPath);
292    err = FSGetCatalogInfo(&fsr, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL);
293    if (err != noErr) osstatusexit(err, "can't get information for '%s'", tempPath);
294    fInfo = (FileInfo *)&(catalogInfo.finderInfo);
295    fInfo->finderFlags |= kIsStationery;
296    err = FSSetCatalogInfo(&fsr, kFSCatInfoFinderInfo, &catalogInfo);
297    if (err != noErr) osstatusexit(err, "can't set information for '%s'", tempPath);
298   
299    return tempPath;
300}
301
302char *stdinAsTempFile() {
303    unsigned char *buf;
304    int bufsize;
305    // Actual number of characters read, and therefore written.
306    ssize_t charCount;
307    int fd;
308    struct stat stat_buf;
309    char *tempFilePath;
310
311    tempFilePath = tempFile(&fd);
312
313    if (fstat(fd, &stat_buf) == -1)
314        errexit("can't fstat temporary file '%s'", tempFilePath);
315
316    bufsize = stat_buf.st_blksize;
317    if ( (buf = (unsigned char *)malloc(bufsize * sizeof(unsigned char))) == NULL)
318        errexit("can't allocate %ld bytes of buffer memory",
319                bufsize * sizeof(unsigned char));
320
321    // Loop until the end of the file.
322    while (1) {
323        // Read a block of input.
324        charCount = read(STDIN_FILENO, buf, bufsize);
325        if (charCount < 0) {
326            errexit("can't read from standard input");
327        }
328        // End of this file?
329        if (charCount == 0)
330            break;
331        // Write this block out.
332        if (write(fd, buf, charCount) != charCount)
333            errexit("error writing to file '%s'", tempFilePath);
334    }
335    free(buf);
336    return tempFilePath;
337}
338
339void getargs(int argc, char * const argv[]) {
340    extern char *optarg;
341    extern int optind;
342    int ch;
343    Boolean appSpecified = false;
344
345    if (argc == 1) usage();
346   
347    while ( (ch = getopt(argc, argv, "npflswbmhCXUc:i:u:a:")) != -1) {
348        switch (ch) {
349        case 'n':
350            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
351            OPTS.action = ACTION_FIND;
352            break;
353        case 'p':
354            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
355            OPTS.action = ACTION_OPEN;
356            LSPEC.launchFlags |= kLSLaunchAndPrint;
357            break;
358        case 'f':
359            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
360            OPTS.action = ACTION_INFO_ITEMS;
361            break;
362        case 'l':
363            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
364            OPTS.action = ACTION_LAUNCH_URLS;
365            break;
366        case 's':
367#ifdef BROKEN_AUTHORIZATION
368            errexit("-s option no longer functional after 10.1 Security Update, sorry");
369#else
370        {
371            AuthorizationRef authRef;
372            AuthorizationItem item = { kAuthorizationRightExecute, strlen(argv[0]), argv[0], 0 };
373            OSStatus err;
374           
375            if (authenticated(item, &authRef)) {
376                continue;
377            }
378            authenticate(item, authRef);
379            err = AuthorizationExecuteWithPrivileges(authRef, argv[0], 0, &argv[1], NULL);
380            if (err != noErr) osstatusexit(err, "unable to launch '%s' with superuser privileges", argv[0]);
381            exit(0); // XXX exit status propagate?
382        }
383#endif
384        case 'w': LSPEC.launchFlags ^= kLSLaunchAsync; break;      // synchronous
385        case 'b': LSPEC.launchFlags |= kLSLaunchDontSwitch; break; // open in background
386        case 'm': LSPEC.launchFlags |= kLSLaunchNewInstance; break;// open multiple
387        case 'h': LSPEC.launchFlags |= kLSLaunchAndHide; break;    // hide once launched
388        case 'C': LSPEC.launchFlags |= kLSLaunchInClassic; break;  // force Classic
389        case 'X': LSPEC.launchFlags ^= kLSLaunchStartClassic; break;// don't start Classic for app
390        case 'U': OPTS.forceURLs = true; break;
391        case 'c':
392            if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long");
393            OPTS.creator = *(OSTypePtr)optarg;
394            appSpecified = true;
395            break;
396        case 'i':
397            OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
398            appSpecified = true;
399            break;
400        case 'a':
401            OPTS.name = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
402            appSpecified = true;
403            break;
404        case 'u':
405            { CFStringRef str = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
406              LSPEC.appURL = CFURLCreateWithString(NULL, str, NULL);
407              if (str != NULL) CFRelease(str);
408            }
409            if (LSPEC.appURL == NULL) {
410                errexit("invalid URL (argument of -u)");
411            } else {
412                CFURLRef absURL = CFURLCopyAbsoluteURL(LSPEC.appURL);
413                CFRelease(LSPEC.appURL);
414                LSPEC.appURL = NULL;
415                if (absURL != NULL) {
416                    CFStringRef scheme = CFURLCopyScheme(absURL);
417                    LSPEC.appURL = absURL;
418                    if (scheme == NULL || !CFEqual(scheme, CFSTR("file")))
419                        errexit("invalid file:// URL (argument of -u)");
420                    CFRelease(scheme);
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                struct stat stat_buf;
479                if (!OPTS.forceURLs && stat(argv[i], &stat_buf) == 0) {
480                    itemURL = NULL;
481                } else {
482                    argStr = CFStringCreateWithCString(NULL, argv[i], kCFStringEncodingUTF8);
483                    // check for URLs
484                    itemURL = normalizedURLFromString(argStr);
485                    if (itemURL == NULL && OPTS.action == ACTION_LAUNCH_URLS) {
486                        // check for email addresses
487                        if (strchr(argv[i], '@') != NULL && strchr(argv[i], '/') == NULL)
488                            itemURL = normalizedURLFromPrefixSlack(CFSTR("mailto:"), argStr);
489                        // check for "slack" URLs
490                        if (itemURL == NULL && strchr(argv[i], '.') != NULL && strchr(argv[i], '/') != argv[i])
491                            itemURL = normalizedURLFromPrefixSlack(CFSTR("http://"), argStr);
492                    }
493                }
494                if (itemURL == NULL) {
495                    // check for file paths
496                    itemURL = CFURLCreateFromFileSystemRepresentation(NULL, argv[i], strlen(argv[i]), false);
497                    err = LSCopyItemInfoForURL(itemURL, kLSRequestExtensionFlagsOnly, &docInfo);
498                    if (err != noErr) osstatusexit(err, "unable to locate '%s'", argv[i]);
499                }
500            }
501            CFArrayAppendValue((CFMutableArrayRef)LSPEC.itemURLs, itemURL);
502            // don't CFRelease the itemURL because CFArray doesn't retain it by default
503            if (argStr != NULL) CFRelease(argStr);
504        }
505    }
506}
507
508Boolean stringFromURLIsRemote(CFURLRef url, char *strBuffer) {
509    CFStringRef scheme = CFURLCopyScheme(url);
510    Boolean isRemote = !CFEqual(scheme, CFSTR("file"));
511    CFRelease(scheme);
512   
513    strBuffer[0] = '\0';
514    if (isRemote) {
515        CFStringRef urlString = CFURLGetString(url);
516        CFStringGetCString(urlString, strBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
517        CFRelease(urlString);
518    } else {
519        if (CFURLGetFileSystemRepresentation(url, false, strBuffer, STRBUF_LEN)) {
520            if (strBuffer[0] == '.' && strBuffer[1] == '/') {
521                // remove the leading "./"
522                char *fromBufPtr = strBuffer + 2;
523                while (true) {
524                    *strBuffer = *fromBufPtr;
525                    if (*fromBufPtr == '\0') break;
526                    strBuffer++;
527                    fromBufPtr++;
528                }
529            }
530        } else {
531            strcpy(strBuffer, "[can't get path: CFURLGetFileSystemRepresentation failed]");
532        }
533    }
534    return isRemote;
535}
536
537void printPathFromURL(CFURLRef url, FILE *stream) {
538    static char strBuffer[STRBUF_LEN];
539    check(url != NULL && stream != NULL);
540    stringFromURLIsRemote(url, strBuffer);
541    fprintf(stream, "%s\n", strBuffer);
542}
543
544void printDateTime(const char *label, UTCDateTime *utcTime, const char *postLabel, Boolean printIfEmpty) {
545    static Str255 dateStr, timeStr;
546    LocalDateTime localTime;
547    LongDateTime longTime;
548    OSStatus err;
549
550    err = ConvertUTCToLocalDateTime(utcTime, &localTime);
551    if (err == kUTCUnderflowErr) {
552        if (printIfEmpty) printf("\t%s: (not set)\n", label);
553        return;
554    }
555    if (err != noErr) osstatusexit(err, "unable to convert UTC %s date to local", label);
556
557    longTime = localTime.highSeconds;
558    longTime <<= 32;
559    longTime |= localTime.lowSeconds;
560
561    // strings include trailing newlines; strip them.
562    LongDateString(&longTime, shortDate, dateStr, nil); dateStr[dateStr[0] + 1] = '\0';
563    LongTimeString(&longTime, true, timeStr, nil); timeStr[timeStr[0] + 1] = '\0';
564    printf("\t%s: %s %s%s\n", label, dateStr + 1, timeStr + 1, postLabel);
565}
566
567#define DFORMAT(SIZE) ((float)(SIZE) / 1024.)
568
569void printSizes(const char *label, UInt64 logicalSize, UInt64 physicalSize, Boolean printIfZero) {
570    UInt32 bigSize = physicalSize >> 32, littleSize = physicalSize;
571    if (!printIfZero && bigSize == 0 && littleSize == 0) return;
572    printf("\t%s: ", label);
573    if (bigSize == 0) {
574        if (littleSize == 0) {
575            printf("zero bytes on disk (zero bytes used)\n"); return;
576        } else if (littleSize < 1024) printf("%lu bytes", littleSize);
577        else {
578            UInt32 adjSize = littleSize >> 10;
579            if (adjSize < 1024) printf("%.1f KB", DFORMAT(littleSize));
580            else {
581                adjSize >>= 10; littleSize >>= 10;
582                if (adjSize < 1024) printf("%.2f MB", DFORMAT(littleSize));
583                else {
584                    adjSize >>= 10; littleSize >>= 10;
585                    printf("%.2f GB", DFORMAT(littleSize));
586                }
587            }
588        }
589    } else {
590        if (bigSize < 256) printf("%lu GB", bigSize);
591        else {
592            bigSize >>= 2;
593            printf("%lu TB", bigSize);
594        }
595    }
596    printf(" on disk (%llu bytes used)\n", logicalSize);
597       
598}
599
600void printMoreInfoFromURL(CFURLRef url) {
601    FSRef fsr;
602    OSStatus err;
603    FSCatalogInfo fscInfo;
604
605    if (!CFURLGetFSRef(url, &fsr)) return;
606    err = FSGetCatalogInfo(&fsr, kFSCatInfoNodeFlags | kFSCatInfoAllDates | kFSCatInfoDataSizes | kFSCatInfoRsrcSizes | kFSCatInfoValence, &fscInfo, NULL, NULL, NULL);
607    if (err != noErr) osstatusexit(err, "unable to get catalog information for file");
608
609    if (fscInfo.nodeFlags & kFSNodeIsDirectoryMask) {
610        printf("\tcontents: ");
611        switch (fscInfo.valence) {
612        case 0: printf("zero items\n"); break;
613        case 1: printf("1 item\n"); break;
614        default: printf("%lu items\n", fscInfo.valence);
615        }
616    } else {
617        printSizes("data fork size", fscInfo.dataLogicalSize, fscInfo.dataPhysicalSize, true);
618        printSizes("rsrc fork size", fscInfo.rsrcLogicalSize, fscInfo.rsrcPhysicalSize, false);
619    }
620
621    if (fscInfo.nodeFlags & (kFSNodeLockedMask | kFSNodeForkOpenMask)) {
622        printf("\tstatus:");
623        if (fscInfo.nodeFlags & kFSNodeLockedMask) {
624            if (fscInfo.nodeFlags & kFSNodeForkOpenMask) printf(" in use,");
625            printf(" locked");
626        } else {
627            printf(" in use");
628        }
629        printf("\n");
630    }
631   
632    printDateTime("created", &fscInfo.createDate, "", true);
633    printDateTime("modified", &fscInfo.contentModDate, "", true);
634    printDateTime("accessed", &fscInfo.accessDate, " [only updated by Mac OS X]", false);
635    printDateTime("backed up", &fscInfo.backupDate, "", false);
636}
637
638const char *utf8StringFromCFStringRef(CFStringRef cfStr) {
639    static char tmpBuffer[STRBUF_LEN];
640    CFStringGetCString(cfStr, tmpBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
641    return tmpBuffer;
642}
643
644const char *utf8StringFromOSType(OSType osType) {
645    CFStringRef typeStr = CFStringCreateWithBytes(NULL, (const char *)&osType, 4, CFStringGetSystemEncoding(), false);
646    if (typeStr == NULL) {
647        // punt to displaying verbatim
648        static char tmpBuffer[4];
649        tmpBuffer[4] = '\0';
650        strncpy(tmpBuffer, (const char *)&osType, 4);
651        return tmpBuffer;
652    }
653    const char *buffer = utf8StringFromCFStringRef(typeStr);
654    CFRelease(typeStr);
655    return buffer;
656}
657
658// 'context' is to match prototype for CFArrayApplierFunction, it's unused
659void printInfoFromURL(CFURLRef url, void *context) {
660    CFStringRef kind;
661    static char strBuffer[STRBUF_LEN];
662   
663    check(url != NULL && context == NULL);
664
665    if (stringFromURLIsRemote(url, strBuffer))
666        printf("<%s>: URL\n", strBuffer);
667    else {
668        static LSItemInfoRecord info;
669        CFStringRef version = NULL;
670        UInt32 intVersion = 0;
671        OSStatus err = LSCopyItemInfoForURL(url, kLSRequestAllInfo, &info);
672        if (err != noErr) osstatusexit(err, "unable to get information about '%s'", strBuffer);
673       
674        printf("%s: ", strBuffer);
675       
676        // modifiers
677        if (info.flags & kLSItemInfoIsInvisible) printf("invisible ");
678        if (info.flags & kLSItemInfoAppIsScriptable) printf("scriptable ");
679        if (info.flags & kLSItemInfoIsNativeApp) printf("Mac OS X ");
680        if (info.flags & kLSItemInfoIsClassicApp) printf("Classic ");
681       
682        // kind
683        if (info.flags & kLSItemInfoIsVolume) printf("volume");
684        else if (info.flags & kLSItemInfoIsApplication) printf("application ");
685        else if (info.flags & kLSItemInfoIsPackage) printf("non-application ");
686        else if (info.flags & kLSItemInfoIsContainer) printf("folder");
687        else if (info.flags & kLSItemInfoIsAliasFile) printf("alias");
688        else if (info.flags & kLSItemInfoIsSymlink) printf("symbolic link");
689        else if (info.flags & kLSItemInfoIsPlainFile) printf("document");
690        else printf("unknown file system entity");
691
692        if (info.flags & kLSItemInfoIsPackage) printf("package ");
693
694        if (info.flags & kLSItemInfoAppPrefersNative) printf("[Carbon, prefers native OS X]");
695        else if (info.flags & kLSItemInfoAppPrefersClassic) printf("[Carbon, prefers Classic]");
696
697        printf("\n");
698        if (!(info.flags & kLSItemInfoIsContainer) || info.flags & kLSItemInfoIsPackage) {
699            printf("\ttype: '%s'", utf8StringFromOSType(info.filetype));
700            printf("\tcreator: '%s'\n", utf8StringFromOSType(info.creator));
701        }
702        if (info.flags & kLSItemInfoIsPackage || info.flags & kLSItemInfoIsApplication) {
703                // a package, or possibly a native app with a 'plst' resource
704            CFBundleRef bundle = CFBundleCreate(NULL, url);
705            CFStringRef bundleID = NULL;
706            if (bundle == NULL && (info.flags & kLSItemInfoIsApplication)) {
707                FSRef fsr;
708                if (info.flags & kLSItemInfoIsPackage || !CFURLGetFSRef(url, &fsr)) {
709                    printf("\t[can't access CFBundle for application]\n");
710                } else { // OS X bug causes this to fail when it shouldn't, so fake it
711                    SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
712                    OSStatus err = ResError();
713                    if (err != noErr) {
714                        printf("\t[can't open resource fork: %s]\n", osstatusstr(err));
715                    } else {
716                        Handle h = Get1Resource('plst', 0);
717                        if ( (err = ResError()) != noErr || h == NULL) {
718                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'plst' 0 resource");
719                        } else {
720                            CFDataRef plstData = CFDataCreate(NULL, *h, GetHandleSize(h));
721                            CFStringRef error;
722                            CFPropertyListRef infoPlist = CFPropertyListCreateFromXMLData(NULL, plstData, kCFPropertyListImmutable, &error);
723                            if (plstData != NULL) {
724                                CFRelease(plstData);
725                                plstData = NULL;
726                            } else {
727                                // 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
728                                infoPlist = CFBundleCopyInfoDictionaryForURL(url);
729                            }
730                            if (infoPlist == NULL) {
731                                printf("\t['plst' 0 resource invalid: %s]\n", utf8StringFromCFStringRef(error));
732                                CFRelease(error);
733                            } else {
734                                // mimic CFBundle logic below
735                                bundleID = CFDictionaryGetValue(infoPlist, kCFBundleIdentifierKey);
736                                if (bundleID != NULL) CFRetain(bundleID);
737                                version = CFDictionaryGetValue(infoPlist, CFSTR("CFBundleShortVersionString"));
738                                if (version == NULL)
739                                    version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey);
740                                if (version != NULL) CFRetain(version);
741                                CFRelease(infoPlist);
742                            }
743                        }
744                        VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
745                        if ( (err = ResError()) != noErr || vers == NULL) {
746                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'vers' 1 resource");
747                        } else {
748                            if (version == NULL) { // prefer 'plst' version
749                                version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
750                            }
751                            intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
752                        }
753                        CloseResFile(resFork);
754                    }
755                }
756            } else {
757                bundleID = CFBundleGetIdentifier(bundle);
758                if (bundleID != NULL) CFRetain(bundleID);
759                // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
760                version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
761                if (version == NULL)
762                    version = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
763                if (version != NULL) {
764                    CFRetain(version);
765                    intVersion = CFBundleGetVersionNumber(bundle);
766                }
767                CFRelease(bundle);
768            }
769            if (bundleID != NULL) {
770                printf("\tbundle ID: %s\n", utf8StringFromCFStringRef(bundleID));
771                CFRelease(bundleID);
772            }
773        } else {
774            // try to get a version if we can, but don't complain if we can't
775            FSRef fsr;
776            if (CFURLGetFSRef(url, &fsr)) {
777                SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
778                if (ResError() == noErr) {
779                    VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
780                    if (ResError() == noErr && vers != NULL) {
781                        version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
782                        intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
783                    }
784                }
785                CloseResFile(resFork);
786            }
787        }
788       
789        if (version != NULL) {
790            printf("\tversion: %s", utf8StringFromCFStringRef(version));
791            if (intVersion != 0) printf(" [0x%lx = %lu]", intVersion, intVersion);
792            putchar('\n');
793            CFRelease(version);
794        }
795
796        // kind string
797        err = LSCopyKindStringForURL(url, &kind);
798        if (err != noErr) osstatusexit(err, "unable to get kind of '%s'", strBuffer);
799        printf("\tkind: %s\n", utf8StringFromCFStringRef(kind));
800        CFRelease(kind);
801        printMoreInfoFromURL(url);
802    }
803}
804
805
806void launchURL(CFURLRef url, ICInstance icInst) {
807    CFStringRef urlStr = CFURLGetString(url);
808    static char strBuffer[STRBUF_LEN];
809    long strStart, strEnd;
810    OSStatus err;
811
812    strBuffer[0] = '\0';
813    CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX no idea what encoding ICLaunchURL is supposed to take; leave as is for now
814    strStart = 0;
815    strEnd = strlen(strBuffer);
816    err = ICLaunchURL(icInst, "\p", strBuffer, strEnd, &strStart, &strEnd);
817    if (err != noErr) {
818        fprintf(stderr, "%s: unable to launch URL <%s>: %s\n", APP_NAME, strBuffer, osstatusstr(err));
819    }
820   
821    CFRelease(urlStr);
822}
823
824OSStatus openFromURLSpec() {
825#ifndef BROKEN_LSOPENFROMURLSPEC
826    return LSOpenFromURLSpec(&LSPEC, NULL);
827#else
828    LSLaunchFSRefSpec spec = {NULL, 0, NULL, LSPEC.passThruParams,
829                              LSPEC.launchFlags, LSPEC.asyncRefCon};
830    CFIndex urlIndex, urlCount = LSPEC.itemURLs ? CFArrayGetCount(LSPEC.itemURLs) : 0;
831    FSRef *itemRefs = malloc(urlCount * sizeof(FSRef));
832    CFURLRef url;
833    CFStringRef scheme, fileScheme = CFSTR("file");
834    int itemIndex = 0;
835    OSStatus err;
836
837    for (urlIndex = 0 ; urlIndex < urlCount ; urlIndex++) {
838        url = CFArrayGetValueAtIndex(LSPEC.itemURLs, urlIndex);
839        scheme = CFURLCopyScheme(url);
840        if (CFEqual(scheme, fileScheme)) {
841            if (CFURLGetFSRef(url, &itemRefs[itemIndex])) {
842                itemIndex++;
843                CFArrayRemoveValueAtIndex((CFMutableArrayRef)LSPEC.itemURLs, urlIndex);
844                urlIndex--;
845                urlCount--;
846            } else {
847                fprintf(stderr, "%s: unable to locate: ", APP_NAME);
848                printPathFromURL(url, stderr);
849            }
850        }
851        CFRelease(scheme);
852    }
853
854    if (urlCount > 0 || itemIndex == 0) { /* URLs, or no items */
855        err = LSOpenFromURLSpec(&LSPEC, NULL);
856        if (err != noErr)
857            return err;
858    }
859    if (itemIndex > 0) {
860        FSRef appRef;
861        spec.numDocs = itemIndex;
862        spec.itemRefs = itemRefs;
863        if (LSPEC.appURL != NULL) {
864            if (!CFURLGetFSRef(LSPEC.appURL, &appRef)) {
865                errexit("can't find application");
866            }
867            spec.appRef = &appRef;
868        }
869        return LSOpenFromRefSpec(&spec, NULL);
870    }
871    return noErr;
872#endif
873}
874
875int main (int argc, char * const argv[]) {
876    OSStatus err;
877   
878    APP_NAME = argv[0];
879    getargs(argc, argv);
880
881    if (OPTS.action == ACTION_FIND || OPTS.action == ACTION_OPEN) {
882        if (LSPEC.appURL != NULL) goto findOK; // already have an application URL
883        err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, NULL, &LSPEC.appURL);
884       
885        if (err != noErr) {
886            if (OPTS.name != NULL && !CFStringHasSuffix(OPTS.name, CFSTR(".app"))) {
887                OPTS.name = CFStringCreateMutableCopy(NULL, CFStringGetLength(OPTS.name) + 4, OPTS.name);
888                CFStringAppend((CFMutableStringRef)OPTS.name, CFSTR(".app"));
889                err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, NULL, &LSPEC.appURL);
890                if (err == noErr) goto findOK;
891            }
892            osstatusexit(err, "can't locate application", argv[1]);
893        findOK: ;
894        }
895    }
896   
897    switch (OPTS.action) {
898    case ACTION_FIND:
899        printPathFromURL(LSPEC.appURL, stdout);
900        break;
901    case ACTION_OPEN:
902        err = openFromURLSpec();
903        if (err != noErr) osstatusexit(err, "can't open application", argv[1]);
904        break;
905    case ACTION_FIND_ITEMS:
906        CFArrayApplyFunction(LSPEC.itemURLs, CFRangeMake(0, CFArrayGetCount(LSPEC.itemURLs)),
907                             (CFArrayApplierFunction) printPathFromURL, stdout);
908        break;
909    case ACTION_OPEN_ITEMS:
910        err = openFromURLSpec();
911        if (err != noErr) osstatusexit(err, "can't open items", argv[1]);
912        break;
913    case ACTION_INFO_ITEMS:
914        CFArrayApplyFunction(LSPEC.itemURLs, CFRangeMake(0, CFArrayGetCount(LSPEC.itemURLs)),
915                             (CFArrayApplierFunction) printInfoFromURL, NULL);
916        break;
917    case ACTION_LAUNCH_URLS:
918    {
919        ICInstance icInst;
920        err = ICStart(&icInst, '\?\?\?\?'); // in case GCC trigraph handling is enabled
921        if (err != noErr) osstatusexit(err, "can't initialize Internet Config", argv[1]);
922        CFArrayApplyFunction(LSPEC.itemURLs, CFRangeMake(0, CFArrayGetCount(LSPEC.itemURLs)),
923                             (CFArrayApplierFunction) launchURL, icInst);
924        ICStop(icInst);
925        break;
926    }
927    }
928
929    if (TEMPFILE != NULL) {
930        // the application may take a while to finish opening the temporary file
931        daemon(0, 0);
932        sleep(60);
933        unlink(TEMPFILE);
934    }
935
936    return 0;
937}
Note: See TracBrowser for help on using the repository browser.