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

Last change on this file since 137 was 137, checked in by Nicholas Riley, 21 years ago

VERSION: Updated for 1.0b3.

main.c: Work around CFBundle bug to report bundle identifiers and
versions from resource forks of unpackaged Carbon applications and
Classic applications [based on code from Lloyd Dupont]. Updated for
1.0b3.

README: Updated for 1.0b3.

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