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

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

VERSION: Updated for 1.1d2.

main.c: Updated for 1.1d2. Added -L. Updated to new date formatting
APIs.

launch.xcodeproj: Set deployment target to 10.4.

README: Updated for 1.1d2.

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