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

Last change on this file since 308 was 308, checked in by Nicholas Riley, 16 years ago

main.c: Display architecture with -f.

launch.xcodeproj: Build in C99 mode.

README: Updated a few examples (the Resorcerer one still needs
fixing).

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