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

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

VERSION: Updated for 1.1d1.

main.c: Updated copyright statement. Updated for 1.1d1. Removed
ancient todo list. Only support 10.4 and higher - removed unnecessary
BROKEN_LSOPENFROMURLSPEC and unused DEBUG #defines. Replace
LSLaunchURLSpec with LSApplicationParameters. Added -o for arguments
(which doesn't work, delaying 1.1's release pending Apple fix).
Support app paths with -a, like open(1) does. Print file content's
UTI if possible on -f. Removed a couple of extraneous arguments to
osstatusexit. Replaced error codes with symbolic constants again.

README: Updated for 1.1d1.

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