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