]> git.saurik.com Git - apple/system_cmds.git/blob - arch.tproj/arch.m
be4b58e30471a1efcd9e3d4315a601c5537bad6e
[apple/system_cmds.git] / arch.tproj / arch.m
1 /*
2 * Copyright (c) 1999, 2006 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <sys/cdefs.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stddef.h>
29 #include <unistd.h>
30 #include <spawn.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <sys/param.h>
34 #include <paths.h>
35 #include <err.h>
36 #include <mach/mach.h>
37 #include <mach-o/arch.h>
38 #include <Foundation/Foundation.h>
39 #include <TargetConditionals.h>
40
41 #ifndef ARCH_PROG
42 #define ARCH_PROG "arch"
43 #endif
44 #define CPUDELTA 10
45 #ifndef MACHINE_PROG
46 #define MACHINE_PROG "machine"
47 #endif
48
49 #define CPUCOUNT(c) ((c)->ptr - (c)->buf)
50
51 static NSMutableDictionary *ArchDict;
52 static NSString *KeyExecPath = @"ExecutablePath";
53 static NSString *KeyPlistVersion = @"PropertyListVersion";
54 static NSString *KeyPrefOrder = @"PreferredOrder";
55 static NSString *PlistExtension = @"plist";
56 static NSString *SettingsDir = @"archSettings";
57
58 static const char envname[] = "ARCHPREFERENCE";
59
60 typedef struct {
61 cpu_type_t *buf;
62 cpu_type_t *ptr;
63 cpu_type_t *end;
64 int errs;
65 } CPU;
66
67 typedef struct {
68 char *str;
69 int i;
70 } StrInt;
71
72 static StrInt initArches[] = {
73 {"i386", CPU_TYPE_I386},
74 {"ppc", CPU_TYPE_POWERPC},
75 {"ppc64", CPU_TYPE_POWERPC64},
76 {"x86_64", CPU_TYPE_X86_64},
77 {NULL, 0}
78 };
79
80 /*
81 * arch - perform the original behavior of the arch and machine commands.
82 * The archcmd flag is non-zero for the arch command, zero for the machine
83 * command. This routine never returns.
84 */
85 static void __dead2
86 arch(int archcmd)
87 {
88 const NXArchInfo *arch = NXGetLocalArchInfo();
89
90 if(!arch)
91 errx(-1, "Unknown architecture.");
92 if(archcmd) {
93 arch = NXGetArchInfoFromCpuType(arch->cputype, CPU_SUBTYPE_MULTIPLE);
94 if(!arch)
95 errx(-1, "Unknown architecture.");
96 }
97 printf("%s%s", arch->name, (isatty(STDIN_FILENO) ? "\n" : ""));
98 exit(0);
99 }
100
101 /*
102 * spawnIt - run the posix_spawn command. count is the number of CPU types
103 * in the prefs array. pflag is non-zero to call posix_spawnp; zero means to
104 * call posix_spawn. str is the name/path to pass to posix_spawn{,p}, and
105 * argv and environ are the argument and environment arrays to pass. This
106 * routine never returns.
107 */
108 static void __dead2
109 spawnIt(int count, cpu_type_t *prefs, int pflag, const char *str, char **argv, char **environ)
110 {
111
112 posix_spawnattr_t attr;
113 pid_t pid;
114 int ret;
115 size_t copied;
116
117 if((ret = posix_spawnattr_init(&attr)) != 0)
118 errc(1, ret, "posix_spawnattr_init");
119 /* do the equivalent of exec, rather than creating a separate process */
120 if((ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC)) != 0)
121 errc(1, ret, "posix_spawnattr_setflags");
122 if((ret = posix_spawnattr_setbinpref_np(&attr, count, prefs, &copied)) != 0)
123 errc(1, ret, "posix_spawnattr_setbinpref_np");
124 if(copied != count)
125 errx(1, "posix_spawnattr_setbinpref_np only copied %d of %d", (int)copied, count);
126 if(pflag)
127 ret = posix_spawnp(&pid, str, NULL, &attr, argv, environ);
128 else
129 ret = posix_spawn(&pid, str, NULL, &attr, argv, environ);
130 errc(1, ret, "posix_spawn%s: %s", (pflag ? "p" : ""), str);
131 }
132
133 /*
134 * initCPU - initialize a CPU structure, a dynamically expanding CPU types
135 * array.
136 */
137 static void
138 initCPU(CPU *cpu)
139 {
140 cpu->buf = (cpu_type_t *)malloc(CPUDELTA * sizeof(cpu_type_t));
141 if(!cpu->buf)
142 err(1, "Failed to malloc CPU buffer");
143 cpu->ptr = cpu->buf;
144 cpu->end = cpu->buf + CPUDELTA;
145 cpu->errs = 0;
146 }
147
148 /*
149 * addCPU - add a new CPU type value to the CPU structure, expanding
150 * the array as necessary.
151 */
152 static void
153 addCPU(CPU *cpu, int n)
154 {
155 if(cpu->ptr >= cpu->end) {
156 cpu_type_t *new = realloc(cpu->buf, (cpu->end - cpu->buf + CPUDELTA) * sizeof(cpu_type_t));
157 if(!new)
158 err(1, "Out of memory realloc-ing CPU structure");
159 ptrdiff_t diff = (void *)new - (void *)cpu->buf;
160 cpu->buf = new;
161 cpu->ptr = (cpu_type_t *)((void *)cpu->ptr + diff);
162 cpu->end = (cpu_type_t *)((void *)cpu->end + diff) + CPUDELTA;
163 }
164 *cpu->ptr++ = n;
165 }
166
167 /*
168 * addCPUbyname - add a new CPU type, given by name, to the CPU structure,
169 * expanding the array as necessary. The name is converted to a type value
170 * by the ArchDict dictionary.
171 */
172 static void
173 addCPUbyname(CPU *cpu, const char *name)
174 {
175 NSNumber *n = (NSNumber *)[ArchDict objectForKey: [[NSString stringWithUTF8String: name] lowercaseString]];
176 if(n == nil) {
177 warnx("Unknown architecture: %s", name);
178 cpu->errs++;
179 return;
180 }
181 addCPU(cpu, [n intValue]);
182 }
183
184 /*
185 * useEnv - parse the environment variable for CPU preferences. Use name
186 * to look for program-specific preferences, and append any CPU types to cpu.
187 * Returns the number of CPU types. Returns any specified execute path in
188 * execpath.
189 *
190 * The environment variable ARCHPREFERENCE has the format:
191 * spec[;spec]...
192 * a semicolon separated list of specifiers. Each specifier has the format:
193 * [prog:[execpath:]]type[,type]...
194 * a comma separate list of CPU type names, optionally proceeded by a program
195 * name and an execpath. If program name exist, that types only apply to that
196 * program. If execpath is specified, it is returned. If no program name
197 * exists, then it applies to all programs. So ordering of the specifiers is
198 * important, as the default (no program name) specifier must be last.
199 */
200 static int
201 useEnv(CPU *cpu, const char *name, char **execpath)
202 {
203 char *val = getenv(envname);
204 if(!val)
205 return 0;
206
207 /* cp will point to the basename of name */
208 const char *cp = strrchr(name, '/');
209 if(cp) {
210 cp++;
211 if(!*cp)
212 errx(1, "%s: no name after last slash", name);
213 } else
214 cp = name;
215 /* make a copy of the environment variable value, so we can modify it */
216 val = strdup(val);
217 if(!val)
218 err(1, "Can't copy environment %s", envname);
219 char *str = val;
220 char *blk;
221 /* for each specifier */
222 while((blk = strsep(&str, ";")) != NULL) {
223 if(*blk == 0)
224 continue; /* two adjacent semicolons */
225 /* now split on colons */
226 char *n = strsep(&blk, ":");
227 if(blk) {
228 char *p = strsep(&blk, ":");
229 if(!blk) { /* there is only one colon, so no execpath */
230 blk = p;
231 p = NULL;
232 } else if(!*p) /* two consecutive colons, so no execpath */
233 p = NULL;
234 if(!*blk)
235 continue; /* no cpu list, so skip */
236 /* if the name matches, or there is no name, process the cpus */
237 if(!*n || strcmp(n, cp) == 0) {
238 if(CPUCOUNT(cpu) <= 0) { /* only if we don't already have cpu types */
239 char *t;
240 while((t = strsep(&blk, ",")) != NULL)
241 addCPUbyname(cpu, t);
242 }
243 *execpath = (*n ? p : NULL); /* only use the exec path is name is set */
244 break;
245 }
246 } else { /* no colons at all, so process as default */
247 if(CPUCOUNT(cpu) <= 0) { /* only if we don't already have cpu types */
248 blk = n;
249 while((n = strsep(&blk, ",")) != NULL)
250 addCPUbyname(cpu, n);
251 }
252 *execpath = NULL;
253 break;
254 }
255 }
256 if(cpu->errs) /* errors during addCPUbyname are fatal */
257 exit(1);
258 return CPUCOUNT(cpu); /* return count of cpus */
259 }
260
261 /*
262 * spawnFromPreference - called when argv[0] is not "arch" or "machine", or
263 * argv[0] was arch, but no commandline architectures were specified.
264 * If the environment variable ARCHPREFERENCE is specified, and there is a
265 * match to argv[0], use the specified cpu preferences. If no exec path
266 * is specified in ARCHPREFERENCE, or no match is found in ARCHPREFERENCE,
267 * get any additional information from a .plist file with the name of argv[0].
268 * This routine never returns.
269 */
270 static void __dead2
271 spawnFromPreferences(CPU *cpu, int needexecpath, char **argv, char **environ)
272 {
273 char *epath = NULL;
274 int count;
275 const char *prog = strrchr(*argv, '/');
276 if(prog)
277 prog++;
278 else
279 prog = *argv;
280 if(!*prog)
281 errx(1, "Not program name specified");
282
283 /* check the environment variable first */
284 if((count = useEnv(cpu, prog, &epath)) > 0) {
285 /* if we were called as arch, use posix_spawnp */
286 if(!needexecpath)
287 spawnIt(count, cpu->buf, 1, (epath ? epath : *argv), argv, environ);
288 /* otherwise, if we have the executable path, call posix_spawn */
289 if(epath)
290 spawnIt(count, cpu->buf, 0, epath, argv, environ);
291 }
292
293 /* pathArray is use to build the .plist file path for each domain */
294 NSMutableArray *pathArray = [NSMutableArray arrayWithCapacity: 3];
295 if(!pathArray)
296 errx(1, "Can't create NSMutableArray");
297 [pathArray addObject: @""]; // placeholder for domain directory
298 [pathArray addObject: SettingsDir];
299 [pathArray addObject: [[NSString stringWithUTF8String: prog] stringByAppendingPathExtension: PlistExtension]];
300
301 /* get the list of top level directories for all domains */
302 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, true);
303 int cnt = [paths count];
304 if(!cnt)
305 errx(1, "No domains");
306
307 /* create the .plist path, and try to read it */
308 int i;
309 NSString *path = NULL;
310 NSData *data = NULL;
311 for(i = 0; i < cnt; i++) {
312 [pathArray replaceObjectAtIndex: 0 withObject: [paths objectAtIndex: i]];
313 path = [NSString pathWithComponents: pathArray];
314 data = [NSData dataWithContentsOfFile: path];
315 if(data) /* found it */
316 break;
317 }
318 if(!data)
319 errx(1, "Can't find any plists for %s", prog);
320
321 /* try to convert the file into a NSDictionary */
322 NSString *error;
323 id plist = [NSPropertyListSerialization propertyListFromData: data mutabilityOption: NSPropertyListImmutable format: nil errorDescription: &error];
324 if(!plist)
325 errx(1, "%s: NSPropertyListSerialization error: %s", [path UTF8String], [error UTF8String]);
326 if(![plist isKindOfClass: [NSDictionary class]])
327 errx(1, "%s: plist not a dictionary", [path UTF8String]);
328
329 NSString *execpath;
330 int errs = 0; /* scan for all errors and fail later */
331 do { /* begin block */
332 /* check the plist version */
333 NSString *vers = [(NSDictionary *)plist objectForKey: KeyPlistVersion];
334 if(!vers) {
335 warnx("%s: No key %s", [path UTF8String], [KeyPlistVersion UTF8String]);
336 errs++;
337 } else if(![vers isKindOfClass: [NSString class]]) {
338 warnx("%s: %s is not a string", [path UTF8String], [KeyPlistVersion UTF8String]);
339 errs++;
340 } else if(![vers isEqualToString: @"1.0"]) {
341 warnx("%s: %s not 1.0", [path UTF8String], [KeyPlistVersion UTF8String]);
342 errs++;
343 }
344 /* get the execpath */
345 execpath = [(NSDictionary *)plist objectForKey: KeyExecPath];
346 if(!execpath) {
347 warnx("%s: No key %s", [path UTF8String], [KeyExecPath UTF8String]);
348 errs++;
349 } else if(![execpath isKindOfClass: [NSString class]]) {
350 warnx("%s: %s is not a string", [path UTF8String], [KeyExecPath UTF8String]);
351 errs++;
352 }
353 /* if we already got cpu preferences from ARCHPREFERENCE, we are done */
354 if(count > 0)
355 break;
356 /* otherwise, parse the cpu preferences from the plist */
357 id p = [(NSDictionary *)plist objectForKey: KeyPrefOrder];
358 if(!p) {
359 warnx("%s: No key %s", [path UTF8String], [KeyPrefOrder UTF8String]);
360 errs++;
361 } else if(![p isKindOfClass: [NSArray class]]) {
362 warnx("%s: %s is not an array", [path UTF8String], [KeyPrefOrder UTF8String]);
363 errs++;
364 } else if((count = [p count]) == 0) {
365 warnx("%s: no entries in %s", [path UTF8String], [KeyPrefOrder UTF8String]);
366 errs++;
367 } else {
368 /* finally but the cpu type array */
369 for(i = 0; i < count; i++) {
370 id a = [(NSArray *)p objectAtIndex: i];
371 NSNumber *n;
372 if(![a isKindOfClass: [NSString class]]) {
373 warnx("%s: entries %d of %s is not a string", [path UTF8String], i, [KeyPrefOrder UTF8String]);
374 errs++;
375 } else if((n = (NSNumber *)[ArchDict objectForKey: [(NSString *)a lowercaseString]]) == nil) {
376 warnx("%s: %s: unknown architecture %s", [path UTF8String], [KeyPrefOrder UTF8String], [(NSString *)a UTF8String]);
377 errs++;
378 } else {
379 addCPU(cpu, [n intValue]);
380 }
381 }
382 }
383 } while(0); /* end block */
384 if(errs) /* exit if there were any reported errors */
385 exit(1);
386
387 /* call posix_spawn */
388 spawnIt(count, cpu->buf, 0, [execpath fileSystemRepresentation], argv, environ);
389 }
390
391 static void __dead2
392 usage(int ret)
393 {
394 fprintf(stderr,
395 "Usage: %s\n"
396 " Display the machine's architecture type\n"
397 "Usage: %s [-h] [[-arch_name | -arch arch_name] ...] prog [arg ...]\n"
398 " Run prog with any arguments, using the given architecture\n"
399 " order. If no architectures are specified, use the\n"
400 " ARCHPREFERENCE environment variable, or a property list file.\n"
401 " -h will print usage message and exit.\n",
402 ARCH_PROG, ARCH_PROG);
403 exit(ret);
404 }
405
406 /*
407 * wrapped - check the path to see if it is a link to /usr/bin/arch.
408 */
409 static int
410 wrapped(const char *name)
411 {
412 int lp, ln;
413 char *p;
414 char *bp = NULL;
415 char *cur, *path;
416 char buf[MAXPATHLEN], rpbuf[MAXPATHLEN];
417 struct stat sb;
418
419 ln = strlen(name);
420
421 do { /* begin block */
422 /* If it's an absolute or relative path name, it's easy. */
423 if(index(name, '/')) {
424 if(stat(name, &sb) == 0 && S_ISREG(sb.st_mode) && access(name, X_OK) == 0) {
425 bp = (char *)name;
426 break;
427 }
428 errx(1, "%s isn't executable", name);
429 }
430
431 /* search the PATH, looking for name */
432 if((path = getenv("PATH")) == NULL)
433 path = _PATH_DEFPATH;
434
435 cur = alloca(strlen(path) + 1);
436 if(cur == NULL)
437 err(1, "alloca");
438 strcpy(cur, path);
439 while((p = strsep(&cur, ":")) != NULL) {
440 /*
441 * It's a SHELL path -- double, leading and trailing colons
442 * mean the current directory.
443 */
444 if(*p == '\0') {
445 p = ".";
446 lp = 1;
447 } else
448 lp = strlen(p);
449
450 /*
451 * If the path is too long complain. This is a possible
452 * security issue; given a way to make the path too long
453 * the user may execute the wrong program.
454 */
455 if(lp + ln + 2 > sizeof(buf)) {
456 warn("%s: path too long", p);
457 continue;
458 }
459 bcopy(p, buf, lp);
460 buf[lp] = '/';
461 bcopy(name, buf + lp + 1, ln);
462 buf[lp + ln + 1] = '\0';
463 if(stat(buf, &sb) == 0 && S_ISREG(sb.st_mode) && access(buf, X_OK) == 0) {
464 bp = buf;
465 break;
466 }
467 }
468 if(p == NULL)
469 errx(1, "Can't find %s in PATH", name);
470 } while(0); /* end block */
471 if(realpath(bp, rpbuf) == NULL)
472 errx(1, "realpath failed on %s", bp);
473 return (strcmp(rpbuf, "/usr/bin/" ARCH_PROG) == 0);
474 }
475
476 /*
477 * spawnFromArgs - called when arch has arguments specified. The arch command
478 * line arguments are:
479 * % arch [[{-xxx | -arch xxx}]...] prog [arg]...
480 * where xxx is a cpu name, and the command to execute and its arguments follow.
481 * If no commandline cpu names are given, the environment variable
482 * ARCHPREFERENCE is used. This routine never returns.
483 */
484 static void __dead2
485 spawnFromArgs(CPU *cpu, char **argv, char **environ)
486 {
487 char *ap;
488
489 /* process cpu options */
490 for(argv++; *argv && **argv == '-'; argv++) {
491 if(strcmp(*argv, "-arch") == 0) {
492 if(*++argv == NULL) {
493 warnx("-arch without architecture");
494 usage(1);
495 }
496 ap = *argv;
497 } else if(strcmp(*argv, "-h") == 0) {
498 usage(0);
499 } else
500 ap = *argv + 1;
501 addCPUbyname(cpu, ap);
502 }
503 if(cpu->errs)
504 exit(1);
505 if(!*argv || !**argv) {
506 warnx("No command to execute");
507 usage(1);
508 }
509 /* if the program is already a link to arch, then force execpath */
510 int needexecpath = wrapped(*argv);
511
512 /*
513 * If we don't have any architecutures, try ARCHPREFERENCE and plist
514 * files.
515 */
516 int count = CPUCOUNT(cpu);
517 if(count <= 0 || needexecpath)
518 spawnFromPreferences(cpu, needexecpath, argv, environ); /* doesn't return */
519
520 /*
521 * Call posix_spawnp on the program name.
522 */
523 spawnIt(count, cpu->buf, 1, *argv, argv, environ);
524 }
525
526 /*
527 * init - initializes the ArchDict dictionary from the values in initArches,
528 * and the CPU structure.
529 */
530 static void
531 init(CPU *cpu)
532 {
533 ArchDict = [NSMutableDictionary dictionaryWithCapacity: 4];
534 if(!ArchDict)
535 errx(1, "Can't create NSMutableDictionary");
536 StrInt *sp;
537 for(sp = initArches; sp->str; sp++) {
538 NSString *s = [NSString stringWithUTF8String: sp->str];
539 if(!s)
540 errx(1, "Can't create NSString for %s", sp->str);
541 NSNumber *n = [NSNumber numberWithInt: sp->i];
542 if(!n)
543 errx(1, "Can't create NSNumber for %s", sp->str);
544 [ArchDict setObject: n forKey: s];
545 }
546 initCPU(cpu);
547 }
548
549 /* the main() routine */
550 int
551 main(int argc, char **argv, char **environ)
552 {
553 const char *prog = getprogname();
554 int my_name_is_arch;
555 CPU cpu;
556
557 if(strcmp(prog, MACHINE_PROG) == 0) {
558 if(argc > 1)
559 errx(-1, "no arguments accepted");
560 arch(0); /* the "machine" command was called */
561 } else if((my_name_is_arch = (strcmp(prog, ARCH_PROG) == 0))) {
562 if(argc == 1)
563 arch(1); /* the "arch" command with no arguments was called */
564 }
565
566 /* set up Objective C autorelease pool */
567 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
568 init(&cpu); /* initialize */
569
570 if(my_name_is_arch)
571 spawnFromArgs(&cpu, argv, environ);
572 else
573 spawnFromPreferences(&cpu, 1, argv, environ);
574 /* should never get here */
575 [pool release];
576 errx(1, "returned from spawn");
577 }