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