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

Last change on this file since 148 was 148, checked in by Nicholas Riley, 19 years ago

main.c: replaced error constants with numbers for 10.3+ LaunchServices errors

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