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

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

main.c: Fix endianness issues in creator code (thanks, Peter Hosey).

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