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

Last change on this file since 661 was 661, checked in by Nicholas Riley, 11 years ago

VERSION: Updated for 1.1.1d5.

main.c: Replace use of daemon(3), which was deprecated in Mac OS X
10.5. Updated for 1.1.1d5.

README: Updated for 1.1.1d5.

Note: man page is out of date, needs updating before 1.1 is released.

Should also investigate enabling BROKEN_ARGUMENTS and either enabling
or removing BROKEN_AUTHORIZATION.

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