osxlaunch.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. * Launcher program for OS X application bundles of PuTTY.
  3. */
  4. /*
  5. * The 'gtk-mac-bundler' utility arranges to build an OS X application
  6. * bundle containing a program compiled against the Quartz GTK
  7. * backend. It does this by including all the necessary GTK shared
  8. * libraries and data files inside the bundle as well as the binary.
  9. *
  10. * But the GTK program won't start up unless all those shared
  11. * libraries etc are already pointed to by environment variables like
  12. * GTK_PATH and PANGO_LIBDIR and things like that, which won't be set
  13. * up when the bundle is launched.
  14. *
  15. * Hence, gtk-mac-bundler expects to install the program in the bundle
  16. * under a name like 'Contents/MacOS/Program-bin'; and the file called
  17. * 'Contents/MacOS/Program', which is the one actually executed when
  18. * the bundle is launched, is a wrapper script that sets up the
  19. * environment before running the actual GTK-using program.
  20. *
  21. * In our case, however, that's not good enough. pterm will want to
  22. * launch subprocesses with general-purpose shell sessions in them,
  23. * and those subprocesses _won't_ want the random stuff dumped in the
  24. * environment by the gtk-mac-bundler standard wrapper script. So I
  25. * have to provide my own wrapper, which has a more complicated job:
  26. * not only setting up the environment for the GTK app, but also
  27. * preserving all details of the _previous_ environment, so that when
  28. * pterm forks off a subprocess to run in a terminal session, it can
  29. * restore the environment that was in force before the wrapper
  30. * started messing about. This source file implements that wrapper,
  31. * and does it in C so as to make string processing more reliable and
  32. * less annoying.
  33. *
  34. * My strategy for saving the old environment is to pick a prefix
  35. * that's unused by anything currently in the environment; let's
  36. * suppose it's "P" for this discussion. Any environment variable I
  37. * overwrite, say "VAR", I will either set "PsVAR=old value", or
  38. * "PuVAR=" ("s" and "u" for "set" and "unset"). Then I pass the
  39. * prefix itself as a command-line argument to the main GTK
  40. * application binary, which then knows how to restore the original
  41. * environment in pterm subprocesses.
  42. */
  43. #include <assert.h>
  44. #include <stdio.h>
  45. #include <stdint.h>
  46. #include <stdlib.h>
  47. #include <string.h>
  48. #if !defined __APPLE__ && !defined TEST_COMPILE_ON_LINUX
  49. /* When we're not compiling for OS X, it's easier to just turn this
  50. * program into a trivial hello-world by ifdef in the source than it
  51. * is to remove it in the makefile edifice. */
  52. int main(int argc, char **argv)
  53. {
  54. fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
  55. return 1;
  56. }
  57. #else /* __APPLE__ */
  58. #include <unistd.h>
  59. #include <libgen.h>
  60. #ifdef __APPLE__
  61. #include <mach-o/dyld.h>
  62. #else
  63. /* For Linux, a bodge to let as much of this code still run as
  64. * possible, so that you can run it under friendly debugging tools
  65. * like valgrind. */
  66. int _NSGetExecutablePath(char *out, uint32_t *outlen)
  67. {
  68. static const char toret[] = "/proc/self/exe";
  69. if (out != NULL && *outlen < sizeof(toret))
  70. return -1;
  71. *outlen = sizeof(toret);
  72. if (out)
  73. memcpy(out, toret, sizeof(toret));
  74. return 0;
  75. }
  76. #endif
  77. /* ----------------------------------------------------------------------
  78. * Find an alphabetic prefix unused by any environment variable name.
  79. */
  80. /*
  81. * This linked-list based system is a bit overkill, but I enjoy an
  82. * algorithmic challenge. We essentially do an incremental radix sort
  83. * of all the existing environment variable names: initially divide
  84. * them into 26 buckets by their first letter (discarding those that
  85. * don't have a letter at that position), then subdivide each bucket
  86. * in turn into 26 sub-buckets, and so on. We maintain each bucket as
  87. * a linked list, and link their heads together into a secondary list
  88. * that functions as a queue (meaning that we go breadth-first,
  89. * processing all the buckets of a given depth before moving on to the
  90. * next depth down). At any stage, if we find one of our 26
  91. * sub-buckets is empty, that's our unused prefix.
  92. *
  93. * The running time is O(number of strings * length of output), and I
  94. * doubt it's possible to do better.
  95. */
  96. #define FANOUT 26
  97. int char_index(int ch)
  98. {
  99. if (ch >= 'A' && ch <= 'Z')
  100. return ch - 'A';
  101. else if (ch >= 'a' && ch <= 'z')
  102. return ch - 'a';
  103. else
  104. return -1;
  105. }
  106. struct bucket {
  107. int prefixlen;
  108. struct bucket *next_bucket;
  109. struct node *first_node;
  110. };
  111. struct node {
  112. const char *string;
  113. int len, prefixlen;
  114. struct node *next;
  115. };
  116. struct node *new_node(struct node *prev_head, const char *string, int len)
  117. {
  118. struct node *ret = (struct node *)malloc(sizeof(struct node));
  119. if (!ret) {
  120. fprintf(stderr, "out of memory\n");
  121. exit(1);
  122. }
  123. ret->next = prev_head;
  124. ret->string = string;
  125. ret->len = len;
  126. return ret;
  127. }
  128. char *get_unused_env_prefix(void)
  129. {
  130. struct bucket *qhead, *qtail;
  131. extern char **environ;
  132. char **e;
  133. qhead = (struct bucket *)malloc(sizeof(struct bucket));
  134. if (!qhead) {
  135. fprintf(stderr, "out of memory\n");
  136. exit(1);
  137. }
  138. qhead->prefixlen = 0;
  139. qhead->first_node = NULL;
  140. qhead->next_bucket = NULL;
  141. for (e = environ; *e; e++)
  142. qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "="));
  143. qtail = qhead;
  144. while (1) {
  145. struct bucket *buckets[FANOUT];
  146. struct node *bucketnode;
  147. int i, index;
  148. for (i = 0; i < FANOUT; i++) {
  149. buckets[i] = (struct bucket *)malloc(sizeof(struct bucket));
  150. if (!buckets[i]) {
  151. fprintf(stderr, "out of memory\n");
  152. exit(1);
  153. }
  154. buckets[i]->prefixlen = qhead->prefixlen + 1;
  155. buckets[i]->first_node = NULL;
  156. qtail->next_bucket = buckets[i];
  157. qtail = buckets[i];
  158. }
  159. qtail->next_bucket = NULL;
  160. bucketnode = qhead->first_node;
  161. while (bucketnode) {
  162. struct node *node = bucketnode;
  163. bucketnode = bucketnode->next;
  164. if (node->len <= qhead->prefixlen)
  165. continue;
  166. index = char_index(node->string[qhead->prefixlen]);
  167. if (!(index >= 0 && index < FANOUT))
  168. continue;
  169. node->prefixlen++;
  170. node->next = buckets[index]->first_node;
  171. buckets[index]->first_node = node;
  172. }
  173. for (i = 0; i < FANOUT; i++) {
  174. if (!buckets[i]->first_node) {
  175. char *ret = malloc(qhead->prefixlen + 2);
  176. if (!ret) {
  177. fprintf(stderr, "out of memory\n");
  178. exit(1);
  179. }
  180. memcpy(ret, qhead->first_node->string, qhead->prefixlen);
  181. ret[qhead->prefixlen] = i + 'A';
  182. ret[qhead->prefixlen + 1] = '\0';
  183. /* This would be where we freed everything, if we
  184. * didn't know it didn't matter because we were
  185. * imminently going to exec another program */
  186. return ret;
  187. }
  188. }
  189. qhead = qhead->next_bucket;
  190. }
  191. }
  192. /* ----------------------------------------------------------------------
  193. * Get the pathname of this executable, so we can locate the rest of
  194. * the app bundle relative to it.
  195. */
  196. /*
  197. * There are several ways to try to retrieve the pathname to the
  198. * running executable:
  199. *
  200. * (a) Declare main() as taking four arguments int main(int argc, char
  201. * **argv, char **envp, char **apple); and look at apple[0].
  202. *
  203. * (b) Use sysctl(KERN_PROCARGS) to get the process arguments for the
  204. * current pid. This involves two steps:
  205. * - sysctl(mib, 2, &argmax, &argmax_size, NULL, 0)
  206. * + mib is an array[2] of int containing
  207. * { CTL_KERN, KERN_ARGMAX }
  208. * + argmax is an int
  209. * + argmax_size is a size_t initialised to sizeof(argmax)
  210. * + returns in argmax the amount of memory you need for the next
  211. * call.
  212. * - sysctl(mib, 3, procargs, &procargs_size, NULL, 0)
  213. * + mib is an array[3] of int containing
  214. * { CTL_KERN, KERN_PROCARGS, current pid }
  215. * + procargs is a buffer of size 'argmax'
  216. * + procargs_size is a size_t initialised to argmax
  217. * + returns in the procargs buffer a collection of
  218. * zero-terminated strings of which the first is the program
  219. * name.
  220. *
  221. * (c) Call _NSGetExecutablePath, once to find out the needed buffer
  222. * size and again to fetch the actual path.
  223. *
  224. * (d) Use Objective-C and Cocoa and call
  225. * [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0].
  226. *
  227. * So, how do those work in various cases? Experiments show:
  228. *
  229. * - if you run the program as 'binary' (or whatever you called it)
  230. * and rely on the shell to search your PATH, all four methods
  231. * return a sensible-looking absolute pathname.
  232. *
  233. * - if you run the program as './binary', (a) and (b) return just
  234. * "./binary", which has a particularly bad race condition if you
  235. * try to convert it into an absolute pathname using realpath(3).
  236. * (c) returns "/full/path/to/./binary", which still needs
  237. * realpath(3)ing to get rid of that ".", but at least it's
  238. * _trying_ to be fully qualified. (d) returns
  239. * "/full/path/to/binary" - full marks!
  240. * + Similar applies if you run it via a more interesting relative
  241. * path such as one with a ".." in: (c) gives you an absolute
  242. * path containing a ".." element, whereas (d) has sorted that
  243. * out.
  244. *
  245. * - if you run the program via a path with a symlink on, _none_ of
  246. * these options successfully returns a path without the symlink.
  247. *
  248. * That last point suggests that even (d) is not a perfect solution on
  249. * its own, and you'll have to realpath() whatever you get back from
  250. * it regardless.
  251. *
  252. * And (d) is extra inconvenient because it returns an NSString, which
  253. * is implicitly Unicode, so it's not clear how you turn that back
  254. * into a char * representing a correct Unix pathname (what charset
  255. * should you interpret it in?). Also because you have to bring in all
  256. * of ObjC and Cocoa, which for a low-level Unix API client like this
  257. * seems like overkill.
  258. *
  259. * So my conclusion is that (c) is most practical for these purposes.
  260. */
  261. char *get_program_path(void)
  262. {
  263. char *our_path;
  264. uint32_t pathlen = 0;
  265. _NSGetExecutablePath(NULL, &pathlen);
  266. our_path = malloc(pathlen);
  267. if (!our_path) {
  268. fprintf(stderr, "out of memory\n");
  269. exit(1);
  270. }
  271. if (_NSGetExecutablePath(our_path, &pathlen)) {
  272. fprintf(stderr, "unable to get launcher executable path\n");
  273. exit(1);
  274. }
  275. /* OS X guarantees to malloc the return value if we pass NULL */
  276. char *our_real_path = realpath(our_path, NULL);
  277. if (!our_real_path) {
  278. fprintf(stderr, "realpath failed\n");
  279. exit(1);
  280. }
  281. free(our_path);
  282. return our_real_path;
  283. }
  284. /* ----------------------------------------------------------------------
  285. * Wrapper on dirname(3) which mallocs its return value to whatever
  286. * size is needed.
  287. */
  288. char *dirname_wrapper(const char *path)
  289. {
  290. char *path_copy = malloc(strlen(path) + 1);
  291. if (!path_copy) {
  292. fprintf(stderr, "out of memory\n");
  293. exit(1);
  294. }
  295. strcpy(path_copy, path);
  296. char *ret_orig = dirname(path_copy);
  297. char *ret = malloc(strlen(ret_orig) + 1);
  298. if (!ret) {
  299. fprintf(stderr, "out of memory\n");
  300. exit(1);
  301. }
  302. strcpy(ret, ret_orig);
  303. free(path_copy);
  304. return ret;
  305. }
  306. /* ----------------------------------------------------------------------
  307. * mallocing string concatenation function.
  308. */
  309. char *alloc_cat(const char *str1, const char *str2)
  310. {
  311. int len1 = strlen(str1), len2 = strlen(str2);
  312. char *ret = malloc(len1 + len2 + 1);
  313. if (!ret) {
  314. fprintf(stderr, "out of memory\n");
  315. exit(1);
  316. }
  317. strcpy(ret, str1);
  318. strcpy(ret + len1, str2);
  319. return ret;
  320. }
  321. /* ----------------------------------------------------------------------
  322. * Overwrite an environment variable, preserving the old one for the
  323. * real app to restore.
  324. */
  325. void setenv_wrap(const char *name, const char *value)
  326. {
  327. #ifdef DEBUG_OSXLAUNCH
  328. printf("setenv(\"%s\",\"%s\")\n", name, value);
  329. #endif
  330. setenv(name, value, 1);
  331. }
  332. void unsetenv_wrap(const char *name)
  333. {
  334. #ifdef DEBUG_OSXLAUNCH
  335. printf("unsetenv(\"%s\")\n", name);
  336. #endif
  337. unsetenv(name);
  338. }
  339. char *prefix, *prefixset, *prefixunset;
  340. void overwrite_env(const char *name, const char *value)
  341. {
  342. const char *oldvalue = getenv(name);
  343. if (oldvalue) {
  344. setenv_wrap(alloc_cat(prefixset, name), oldvalue);
  345. } else {
  346. setenv_wrap(alloc_cat(prefixunset, name), "");
  347. }
  348. if (value)
  349. setenv_wrap(name, value);
  350. else
  351. unsetenv_wrap(name);
  352. }
  353. /* ----------------------------------------------------------------------
  354. * Main program.
  355. */
  356. int main(int argc, char **argv)
  357. {
  358. prefix = get_unused_env_prefix();
  359. prefixset = alloc_cat(prefix, "s");
  360. prefixunset = alloc_cat(prefix, "u");
  361. #ifdef DEBUG_OSXLAUNCH
  362. printf("Environment prefixes: main=\"%s\", set=\"%s\", unset=\"%s\"\n",
  363. prefix, prefixset, prefixunset);
  364. #endif
  365. char *prog_path = get_program_path(); // <bundle>/Contents/MacOS/<filename>
  366. char *macos = dirname_wrapper(prog_path); // <bundle>/Contents/MacOS
  367. char *contents = dirname_wrapper(macos); // <bundle>/Contents
  368. // char *bundle = dirname_wrapper(contents); // <bundle>
  369. char *resources = alloc_cat(contents, "/Resources");
  370. // char *bin = alloc_cat(resources, "/bin");
  371. char *etc = alloc_cat(resources, "/etc");
  372. char *lib = alloc_cat(resources, "/lib");
  373. char *share = alloc_cat(resources, "/share");
  374. char *xdg = alloc_cat(etc, "/xdg");
  375. // char *gtkrc = alloc_cat(etc, "/gtk-2.0/gtkrc");
  376. char *locale = alloc_cat(share, "/locale");
  377. char *realbin = alloc_cat(prog_path, "-bin");
  378. // overwrite_env("DYLD_LIBRARY_PATH", lib);
  379. overwrite_env("XDG_CONFIG_DIRS", xdg);
  380. overwrite_env("XDG_DATA_DIRS", share);
  381. overwrite_env("GTK_DATA_PREFIX", resources);
  382. overwrite_env("GTK_EXE_PREFIX", resources);
  383. overwrite_env("GTK_PATH", resources);
  384. overwrite_env("PANGO_LIBDIR", lib);
  385. overwrite_env("PANGO_SYSCONFDIR", etc);
  386. overwrite_env("I18NDIR", locale);
  387. overwrite_env("LANG", NULL);
  388. overwrite_env("LC_MESSAGES", NULL);
  389. overwrite_env("LC_MONETARY", NULL);
  390. overwrite_env("LC_COLLATE", NULL);
  391. char **new_argv = malloc((argc + 16) * sizeof(const char *));
  392. if (!new_argv) {
  393. fprintf(stderr, "out of memory\n");
  394. exit(1);
  395. }
  396. int j = 0;
  397. new_argv[j++] = realbin;
  398. #ifdef DEBUG_OSXLAUNCH
  399. printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]);
  400. #endif
  401. {
  402. int i = 1;
  403. if (i < argc && !strncmp(argv[i], "-psn_", 5))
  404. i++;
  405. for (; i < argc; i++) {
  406. new_argv[j++] = argv[i];
  407. #ifdef DEBUG_OSXLAUNCH
  408. printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]);
  409. #endif
  410. }
  411. }
  412. new_argv[j++] = prefix;
  413. #ifdef DEBUG_OSXLAUNCH
  414. printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]);
  415. #endif
  416. new_argv[j++] = NULL;
  417. #ifdef DEBUG_OSXLAUNCH
  418. printf("executing \"%s\"\n", realbin);
  419. #endif
  420. execv(realbin, new_argv);
  421. perror("execv");
  422. free(new_argv);
  423. free(contents);
  424. free(macos);
  425. return 127;
  426. }
  427. #endif /* __APPLE__ */