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

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

display alias targets

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