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

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

VERSION: Updated for 1.1.1d1.

main.c: Updated error list for 10.4, a bit belatedly.

README: Updated for 1.1.1d1.

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