osxlaunch.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. * DYLD_LIBRARY_PATH, which won't be set up when the bundle is
  13. * 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. #ifndef __APPLE__
  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. #include <mach-o/dyld.h>
  61. /* ----------------------------------------------------------------------
  62. * Find an alphabetic prefix unused by any environment variable name.
  63. */
  64. /*
  65. * This linked-list based system is a bit overkill, but I enjoy an
  66. * algorithmic challenge. We essentially do an incremental radix sort
  67. * of all the existing environment variable names: initially divide
  68. * them into 26 buckets by their first letter (discarding those that
  69. * don't have a letter at that position), then subdivide each bucket
  70. * in turn into 26 sub-buckets, and so on. We maintain each bucket as
  71. * a linked list, and link their heads together into a secondary list
  72. * that functions as a queue (meaning that we go breadth-first,
  73. * processing all the buckets of a given depth before moving on to the
  74. * next depth down). At any stage, if we find one of our 26
  75. * sub-buckets is empty, that's our unused prefix.
  76. *
  77. * The running time is O(number of strings * length of output), and I
  78. * doubt it's possible to do better.
  79. */
  80. #define FANOUT 26
  81. int char_index(int ch)
  82. {
  83. if (ch >= 'A' && ch <= 'Z')
  84. return ch - 'A';
  85. else if (ch >= 'a' && ch <= 'z')
  86. return ch - 'a';
  87. else
  88. return -1;
  89. }
  90. struct bucket {
  91. int prefixlen;
  92. struct bucket *next_bucket;
  93. struct node *first_node;
  94. };
  95. struct node {
  96. const char *string;
  97. int len, prefixlen;
  98. struct node *next;
  99. };
  100. struct node *new_node(struct node *prev_head, const char *string, int len)
  101. {
  102. struct node *ret = (struct node *)malloc(sizeof(struct node));
  103. if (!ret) {
  104. fprintf(stderr, "out of memory\n");
  105. exit(1);
  106. }
  107. ret->next = prev_head;
  108. ret->string = string;
  109. ret->len = len;
  110. return ret;
  111. }
  112. char *get_unused_env_prefix(void)
  113. {
  114. struct bucket *qhead, *qtail;
  115. extern char **environ;
  116. char **e;
  117. qhead = (struct bucket *)malloc(sizeof(struct bucket));
  118. qhead->prefixlen = 0;
  119. if (!qhead) {
  120. fprintf(stderr, "out of memory\n");
  121. exit(1);
  122. }
  123. for (e = environ; *e; e++)
  124. qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "="));
  125. qtail = qhead;
  126. while (1) {
  127. struct bucket *buckets[FANOUT];
  128. struct node *bucketnode;
  129. int i, index;
  130. for (i = 0; i < FANOUT; i++) {
  131. buckets[i] = (struct bucket *)malloc(sizeof(struct bucket));
  132. if (!buckets[i]) {
  133. fprintf(stderr, "out of memory\n");
  134. exit(1);
  135. }
  136. buckets[i]->prefixlen = qhead->prefixlen + 1;
  137. qtail->next_bucket = buckets[i];
  138. qtail = buckets[i];
  139. }
  140. qtail->next_bucket = NULL;
  141. bucketnode = qhead->first_node;
  142. while (bucketnode) {
  143. struct node *node = bucketnode;
  144. bucketnode = bucketnode->next;
  145. if (node->len <= qhead->prefixlen)
  146. continue;
  147. index = char_index(node->string[qhead->prefixlen]);
  148. if (!(index >= 0 && index < FANOUT))
  149. continue;
  150. node->prefixlen++;
  151. node->next = buckets[index]->first_node;
  152. buckets[index]->first_node = node;
  153. }
  154. for (i = 0; i < FANOUT; i++) {
  155. if (!buckets[i]->first_node) {
  156. char *ret = malloc(qhead->prefixlen + 2);
  157. if (!ret) {
  158. fprintf(stderr, "out of memory\n");
  159. exit(1);
  160. }
  161. memcpy(ret, qhead->first_node->string, qhead->prefixlen);
  162. ret[qhead->prefixlen] = i + 'A';
  163. ret[qhead->prefixlen + 1] = '\0';
  164. /* This would be where we freed everything, if we
  165. * didn't know it didn't matter because we were
  166. * imminently going to exec another program */
  167. return ret;
  168. }
  169. }
  170. qhead = qhead->next_bucket;
  171. }
  172. }
  173. /* ----------------------------------------------------------------------
  174. * Get the pathname of this executable, so we can locate the rest of
  175. * the app bundle relative to it.
  176. */
  177. /*
  178. * There are several ways to try to retrieve the pathname to the
  179. * running executable:
  180. *
  181. * (a) Declare main() as taking four arguments int main(int argc, char
  182. * **argv, char **envp, char **apple); and look at apple[0].
  183. *
  184. * (b) Use sysctl(KERN_PROCARGS) to get the process arguments for the
  185. * current pid. This involves two steps:
  186. * - sysctl(mib, 2, &argmax, &argmax_size, NULL, 0)
  187. * + mib is an array[2] of int containing
  188. * { CTL_KERN, KERN_ARGMAX }
  189. * + argmax is an int
  190. * + argmax_size is a size_t initialised to sizeof(argmax)
  191. * + returns in argmax the amount of memory you need for the next
  192. * call.
  193. * - sysctl(mib, 3, procargs, &procargs_size, NULL, 0)
  194. * + mib is an array[3] of int containing
  195. * { CTL_KERN, KERN_PROCARGS, current pid }
  196. * + procargs is a buffer of size 'argmax'
  197. * + procargs_size is a size_t initialised to argmax
  198. * + returns in the procargs buffer a collection of
  199. * zero-terminated strings of which the first is the program
  200. * name.
  201. *
  202. * (c) Call _NSGetExecutablePath, once to find out the needed buffer
  203. * size and again to fetch the actual path.
  204. *
  205. * (d) Use Objective-C and Cocoa and call
  206. * [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0].
  207. *
  208. * So, how do those work in various cases? Experiments show:
  209. *
  210. * - if you run the program as 'binary' (or whatever you called it)
  211. * and rely on the shell to search your PATH, all four methods
  212. * return a sensible-looking absolute pathname.
  213. *
  214. * - if you run the program as './binary', (a) and (b) return just
  215. * "./binary", which has a particularly bad race condition if you
  216. * try to convert it into an absolute pathname using realpath(3).
  217. * (c) returns "/full/path/to/./binary", which still needs
  218. * realpath(3)ing to get rid of that ".", but at least it's
  219. * _trying_ to be fully qualified. (d) returns
  220. * "/full/path/to/binary" - full marks!
  221. * + Similar applies if you run it via a more interesting relative
  222. * path such as one with a ".." in: (c) gives you an absolute
  223. * path containing a ".." element, whereas (d) has sorted that
  224. * out.
  225. *
  226. * - if you run the program via a path with a symlink on, _none_ of
  227. * these options successfully returns a path without the symlink.
  228. *
  229. * That last point suggests that even (d) is not a perfect solution on
  230. * its own, and you'll have to realpath() whatever you get back from
  231. * it regardless.
  232. *
  233. * And (d) is extra inconvenient because it returns an NSString, which
  234. * is implicitly Unicode, so it's not clear how you turn that back
  235. * into a char * representing a correct Unix pathname (what charset
  236. * should you interpret it in?). Also because you have to bring in all
  237. * of ObjC and Cocoa, which for a low-level Unix API client like this
  238. * seems like overkill.
  239. *
  240. * So my conclusion is that (c) is most practical for these purposes.
  241. */
  242. char *get_program_path(void)
  243. {
  244. char *our_path;
  245. uint32_t pathlen = 0;
  246. _NSGetExecutablePath(NULL, &pathlen);
  247. our_path = malloc(pathlen);
  248. if (!our_path) {
  249. fprintf(stderr, "out of memory\n");
  250. exit(1);
  251. }
  252. if (_NSGetExecutablePath(our_path, &pathlen)) {
  253. fprintf(stderr, "unable to get launcher executable path\n");
  254. exit(1);
  255. }
  256. /* OS X guarantees to malloc the return value if we pass NULL */
  257. char *our_real_path = realpath(our_path, NULL);
  258. if (!our_real_path) {
  259. fprintf(stderr, "realpath failed\n");
  260. exit(1);
  261. }
  262. free(our_path);
  263. return our_real_path;
  264. }
  265. /* ----------------------------------------------------------------------
  266. * Wrapper on dirname(3) which mallocs its return value to whatever
  267. * size is needed.
  268. */
  269. char *dirname_wrapper(const char *path)
  270. {
  271. char *path_copy = malloc(strlen(path) + 1);
  272. if (!path_copy) {
  273. fprintf(stderr, "out of memory\n");
  274. exit(1);
  275. }
  276. strcpy(path_copy, path);
  277. char *ret_orig = dirname(path_copy);
  278. char *ret = malloc(strlen(ret_orig) + 1);
  279. if (!ret) {
  280. fprintf(stderr, "out of memory\n");
  281. exit(1);
  282. }
  283. strcpy(ret, ret_orig);
  284. free(path_copy);
  285. return ret;
  286. }
  287. /* ----------------------------------------------------------------------
  288. * mallocing string concatenation function.
  289. */
  290. char *alloc_cat(const char *str1, const char *str2)
  291. {
  292. int len1 = strlen(str1), len2 = strlen(str2);
  293. char *ret = malloc(len1 + len2 + 1);
  294. if (!ret) {
  295. fprintf(stderr, "out of memory\n");
  296. exit(1);
  297. }
  298. strcpy(ret, str1);
  299. strcpy(ret + len1, str2);
  300. return ret;
  301. }
  302. /* ----------------------------------------------------------------------
  303. * Overwrite an environment variable, preserving the old one for the
  304. * real app to restore.
  305. */
  306. char *prefix, *prefixset, *prefixunset;
  307. void overwrite_env(const char *name, const char *value)
  308. {
  309. const char *oldvalue = getenv(name);
  310. if (oldvalue) {
  311. setenv(alloc_cat(prefixset, name), oldvalue, 1);
  312. } else {
  313. setenv(alloc_cat(prefixunset, name), "", 1);
  314. }
  315. if (value)
  316. setenv(name, value, 1);
  317. else
  318. unsetenv(name);
  319. }
  320. /* ----------------------------------------------------------------------
  321. * Main program.
  322. */
  323. int main(int argc, char **argv)
  324. {
  325. prefix = get_unused_env_prefix();
  326. prefixset = alloc_cat(prefix, "s");
  327. prefixunset = alloc_cat(prefix, "u");
  328. char *prog_path = get_program_path(); // <bundle>/Contents/MacOS/<filename>
  329. char *macos = dirname_wrapper(prog_path); // <bundle>/Contents/MacOS
  330. char *contents = dirname_wrapper(macos); // <bundle>/Contents
  331. // char *bundle = dirname_wrapper(contents); // <bundle>
  332. char *resources = alloc_cat(contents, "/Resources");
  333. // char *bin = alloc_cat(resources, "/bin");
  334. char *etc = alloc_cat(resources, "/etc");
  335. char *lib = alloc_cat(resources, "/lib");
  336. char *share = alloc_cat(resources, "/share");
  337. char *xdg = alloc_cat(etc, "/xdg");
  338. // char *gtkrc = alloc_cat(etc, "/gtk-2.0/gtkrc");
  339. char *locale = alloc_cat(share, "/locale");
  340. char *realbin = alloc_cat(prog_path, "-bin");
  341. overwrite_env("DYLD_LIBRARY_PATH", lib);
  342. overwrite_env("XDG_CONFIG_DIRS", xdg);
  343. overwrite_env("XDG_DATA_DIRS", share);
  344. overwrite_env("GTK_DATA_PREFIX", resources);
  345. overwrite_env("GTK_EXE_PREFIX", resources);
  346. overwrite_env("GTK_PATH", resources);
  347. overwrite_env("PANGO_LIBDIR", lib);
  348. overwrite_env("PANGO_SYSCONFDIR", etc);
  349. overwrite_env("I18NDIR", locale);
  350. overwrite_env("LANG", NULL);
  351. overwrite_env("LC_MESSAGES", NULL);
  352. overwrite_env("LC_MONETARY", NULL);
  353. overwrite_env("LC_COLLATE", NULL);
  354. char **new_argv = malloc((argc + 16) * sizeof(const char *));
  355. if (!new_argv) {
  356. fprintf(stderr, "out of memory\n");
  357. exit(1);
  358. }
  359. int j = 0;
  360. new_argv[j++] = realbin;
  361. {
  362. int i = 1;
  363. if (i < argc && !strncmp(argv[i], "-psn_", 5))
  364. i++;
  365. for (; i < argc; i++)
  366. new_argv[j++] = argv[i];
  367. }
  368. new_argv[j++] = prefix;
  369. new_argv[j++] = NULL;
  370. execv(realbin, new_argv);
  371. perror("execv");
  372. return 127;
  373. }
  374. #endif /* __APPLE__ */