2 * Launcher program for OS X application bundles of PuTTY.
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.
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
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.
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
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.
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)
57 fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
64 #include <mach-o/dyld.h>
66 /* ----------------------------------------------------------------------
67 * Find an alphabetic prefix unused by any environment variable name.
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.
83 * The running time is O(number of strings * length of output), and I
84 * doubt it's possible to do better.
88 int char_index(int ch)
90 if (ch >= 'A' && ch <= 'Z')
92 else if (ch >= 'a' && ch <= 'z')
100 struct bucket *next_bucket;
101 struct node *first_node;
110 struct node *new_node(struct node *prev_head, const char *string, int len)
112 struct node *ret = (struct node *)malloc(sizeof(struct node));
115 fprintf(stderr, "out of memory\n");
119 ret->next = prev_head;
120 ret->string = string;
126 char *get_unused_env_prefix(void)
128 struct bucket *qhead, *qtail;
129 extern char **environ;
132 qhead = (struct bucket *)malloc(sizeof(struct bucket));
133 qhead->prefixlen = 0;
135 fprintf(stderr, "out of memory\n");
138 for (e = environ; *e; e++)
139 qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "="));
143 struct bucket *buckets[FANOUT];
144 struct node *bucketnode;
147 for (i = 0; i < FANOUT; i++) {
148 buckets[i] = (struct bucket *)malloc(sizeof(struct bucket));
150 fprintf(stderr, "out of memory\n");
153 buckets[i]->prefixlen = qhead->prefixlen + 1;
154 qtail->next_bucket = buckets[i];
157 qtail->next_bucket = NULL;
159 bucketnode = qhead->first_node;
161 struct node *node = bucketnode;
162 bucketnode = bucketnode->next;
164 if (node->len <= qhead->prefixlen)
166 index = char_index(node->string[qhead->prefixlen]);
167 if (!(index >= 0 && index < FANOUT))
170 node->next = buckets[index]->first_node;
171 buckets[index]->first_node = node;
174 for (i = 0; i < FANOUT; i++) {
175 if (!buckets[i]->first_node) {
176 char *ret = malloc(qhead->prefixlen + 2);
178 fprintf(stderr, "out of memory\n");
181 memcpy(ret, qhead->first_node->string, qhead->prefixlen);
182 ret[qhead->prefixlen] = i + 'A';
183 ret[qhead->prefixlen + 1] = '\0';
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 */
192 qhead = qhead->next_bucket;
196 /* ----------------------------------------------------------------------
197 * Get the pathname of this executable, so we can locate the rest of
198 * the app bundle relative to it.
202 * There are several ways to try to retrieve the pathname to the
203 * running executable:
205 * (a) Declare main() as taking four arguments int main(int argc, char
206 * **argv, char **envp, char **apple); and look at apple[0].
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 }
214 * + argmax_size is a size_t initialised to sizeof(argmax)
215 * + returns in argmax the amount of memory you need for the next
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
226 * (c) Call _NSGetExecutablePath, once to find out the needed buffer
227 * size and again to fetch the actual path.
229 * (d) Use Objective-C and Cocoa and call
230 * [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0].
232 * So, how do those work in various cases? Experiments show:
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.
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
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.
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
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.
264 * So my conclusion is that (c) is most practical for these purposes.
267 char *get_program_path(void)
270 uint32_t pathlen = 0;
271 _NSGetExecutablePath(NULL, &pathlen);
272 our_path = malloc(pathlen);
274 fprintf(stderr, "out of memory\n");
277 if (_NSGetExecutablePath(our_path, &pathlen)) {
278 fprintf(stderr, "unable to get launcher executable path\n");
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");
290 return our_real_path;
293 /* ----------------------------------------------------------------------
294 * Wrapper on dirname(3) which mallocs its return value to whatever
298 char *dirname_wrapper(const char *path)
300 char *path_copy = malloc(strlen(path) + 1);
302 fprintf(stderr, "out of memory\n");
305 strcpy(path_copy, path);
306 char *ret_orig = dirname(path_copy);
307 char *ret = malloc(strlen(ret_orig) + 1);
309 fprintf(stderr, "out of memory\n");
312 strcpy(ret, ret_orig);
317 /* ----------------------------------------------------------------------
318 * mallocing string concatenation function.
321 char *alloc_cat(const char *str1, const char *str2)
323 int len1 = strlen(str1), len2 = strlen(str2);
324 char *ret = malloc(len1 + len2 + 1);
326 fprintf(stderr, "out of memory\n");
330 strcpy(ret + len1, str2);
334 /* ----------------------------------------------------------------------
335 * Overwrite an environment variable, preserving the old one for the
336 * real app to restore.
338 char *prefix, *prefixset, *prefixunset;
339 void overwrite_env(const char *name, const char *value)
341 const char *oldvalue = getenv(name);
343 setenv(alloc_cat(prefixset, name), oldvalue, 1);
345 setenv(alloc_cat(prefixunset, name), "", 1);
348 setenv(name, value, 1);
353 /* ----------------------------------------------------------------------
357 int main(int argc, char **argv)
359 prefix = get_unused_env_prefix();
360 prefixset = alloc_cat(prefix, "s");
361 prefixunset = alloc_cat(prefix, "u");
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");
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);
391 char **new_argv = malloc((argc + 16) * sizeof(const char *));
393 fprintf(stderr, "out of memory\n");
397 new_argv[j++] = realbin;
400 if (i < argc && !strncmp(argv[i], "-psn_", 5))
403 for (; i < argc; i++)
404 new_argv[j++] = argv[i];
406 new_argv[j++] = prefix;
407 new_argv[j++] = NULL;
409 execv(realbin, new_argv);
414 #endif /* __APPLE__ */