source: releases/launch/1.1/launch/main.c@ 335

Last change on this file since 335 was 309, checked in by Nicholas Riley, 17 years ago

VERSION: Updated for 1.1.

main.c: Disabled -o since it doesn't work.

launch.1: Updated for 1.1. Added description of -a which had been
missing (oops).

README: Updated for 1.1. Turned out the Resorcerer example is still
fine (though rather out of date contextually, oh well.)

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