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