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

Last change on this file since 166 was 166, checked in by Nicholas Riley, 17 years ago

VERSION: Updated for 1.0.1.

main.c: Updated copyright statement. Updated for 1.0.1. Added -U,
triggers OPTS.forceURLs. Added kLSMultipleSessionsNotSupportedErr,
nsvErr. Cleaned up string encoding handling; works much better now.
Split code into stringFromURLIsRemote, utf8StringFromCFStringRef, and
utf8StringFromOSType. Display "contents: zero items" instead of "0
items" in printMoreInfoForURL. Remove extraneous "./" at beginning of
displayed paths. Get versions of non-{applications, packages} and
info from nonbundled apps with CFBundleCopyInfoDictionaryForURL.
Replaced some error codes with numbers so we support building on 10.2
again.

launch.1: Updated for 1.0.1 and -U option.

README: Updated for 1.0.1. Fixed a paste-o in the uninstallation
instructions.

package-launch.sh: Use zsh explicitly. Build as deployment. Fix
permissions. Make tarball contents owned by root/wheel.

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