123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- /*
- * Launcher program for OS X application bundles of PuTTY.
- */
- /*
- * The 'gtk-mac-bundler' utility arranges to build an OS X application
- * bundle containing a program compiled against the Quartz GTK
- * backend. It does this by including all the necessary GTK shared
- * libraries and data files inside the bundle as well as the binary.
- *
- * But the GTK program won't start up unless all those shared
- * libraries etc are already pointed to by environment variables like
- * GTK_PATH and PANGO_LIBDIR and things like that, which won't be set
- * up when the bundle is launched.
- *
- * Hence, gtk-mac-bundler expects to install the program in the bundle
- * under a name like 'Contents/MacOS/Program-bin'; and the file called
- * 'Contents/MacOS/Program', which is the one actually executed when
- * the bundle is launched, is a wrapper script that sets up the
- * environment before running the actual GTK-using program.
- *
- * In our case, however, that's not good enough. pterm will want to
- * launch subprocesses with general-purpose shell sessions in them,
- * and those subprocesses _won't_ want the random stuff dumped in the
- * environment by the gtk-mac-bundler standard wrapper script. So I
- * have to provide my own wrapper, which has a more complicated job:
- * not only setting up the environment for the GTK app, but also
- * preserving all details of the _previous_ environment, so that when
- * pterm forks off a subprocess to run in a terminal session, it can
- * restore the environment that was in force before the wrapper
- * started messing about. This source file implements that wrapper,
- * and does it in C so as to make string processing more reliable and
- * less annoying.
- *
- * My strategy for saving the old environment is to pick a prefix
- * that's unused by anything currently in the environment; let's
- * suppose it's "P" for this discussion. Any environment variable I
- * overwrite, say "VAR", I will either set "PsVAR=old value", or
- * "PuVAR=" ("s" and "u" for "set" and "unset"). Then I pass the
- * prefix itself as a command-line argument to the main GTK
- * application binary, which then knows how to restore the original
- * environment in pterm subprocesses.
- */
- #include <assert.h>
- #include <stdio.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <string.h>
- #if !defined __APPLE__ && !defined TEST_COMPILE_ON_LINUX
- /* When we're not compiling for OS X, it's easier to just turn this
- * program into a trivial hello-world by ifdef in the source than it
- * is to remove it in the makefile edifice. */
- int main(int argc, char **argv)
- {
- fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
- return 1;
- }
- #else /* __APPLE__ */
- #include <unistd.h>
- #include <libgen.h>
- #ifdef __APPLE__
- #include <mach-o/dyld.h>
- #else
- /* For Linux, a bodge to let as much of this code still run as
- * possible, so that you can run it under friendly debugging tools
- * like valgrind. */
- int _NSGetExecutablePath(char *out, uint32_t *outlen)
- {
- static const char toret[] = "/proc/self/exe";
- if (out != NULL && *outlen < sizeof(toret))
- return -1;
- *outlen = sizeof(toret);
- if (out)
- memcpy(out, toret, sizeof(toret));
- return 0;
- }
- #endif
- /* ----------------------------------------------------------------------
- * Find an alphabetic prefix unused by any environment variable name.
- */
- /*
- * This linked-list based system is a bit overkill, but I enjoy an
- * algorithmic challenge. We essentially do an incremental radix sort
- * of all the existing environment variable names: initially divide
- * them into 26 buckets by their first letter (discarding those that
- * don't have a letter at that position), then subdivide each bucket
- * in turn into 26 sub-buckets, and so on. We maintain each bucket as
- * a linked list, and link their heads together into a secondary list
- * that functions as a queue (meaning that we go breadth-first,
- * processing all the buckets of a given depth before moving on to the
- * next depth down). At any stage, if we find one of our 26
- * sub-buckets is empty, that's our unused prefix.
- *
- * The running time is O(number of strings * length of output), and I
- * doubt it's possible to do better.
- */
- #define FANOUT 26
- int char_index(int ch)
- {
- if (ch >= 'A' && ch <= 'Z')
- return ch - 'A';
- else if (ch >= 'a' && ch <= 'z')
- return ch - 'a';
- else
- return -1;
- }
- struct bucket {
- int prefixlen;
- struct bucket *next_bucket;
- struct node *first_node;
- };
- struct node {
- const char *string;
- int len, prefixlen;
- struct node *next;
- };
- struct node *new_node(struct node *prev_head, const char *string, int len)
- {
- struct node *ret = (struct node *)malloc(sizeof(struct node));
- if (!ret) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- ret->next = prev_head;
- ret->string = string;
- ret->len = len;
- return ret;
- }
- char *get_unused_env_prefix(void)
- {
- struct bucket *qhead, *qtail;
- extern char **environ;
- char **e;
- qhead = (struct bucket *)malloc(sizeof(struct bucket));
- if (!qhead) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- qhead->prefixlen = 0;
- qhead->first_node = NULL;
- qhead->next_bucket = NULL;
- for (e = environ; *e; e++)
- qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "="));
- qtail = qhead;
- while (1) {
- struct bucket *buckets[FANOUT];
- struct node *bucketnode;
- int i, index;
- for (i = 0; i < FANOUT; i++) {
- buckets[i] = (struct bucket *)malloc(sizeof(struct bucket));
- if (!buckets[i]) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- buckets[i]->prefixlen = qhead->prefixlen + 1;
- buckets[i]->first_node = NULL;
- qtail->next_bucket = buckets[i];
- qtail = buckets[i];
- }
- qtail->next_bucket = NULL;
- bucketnode = qhead->first_node;
- while (bucketnode) {
- struct node *node = bucketnode;
- bucketnode = bucketnode->next;
- if (node->len <= qhead->prefixlen)
- continue;
- index = char_index(node->string[qhead->prefixlen]);
- if (!(index >= 0 && index < FANOUT))
- continue;
- node->prefixlen++;
- node->next = buckets[index]->first_node;
- buckets[index]->first_node = node;
- }
- for (i = 0; i < FANOUT; i++) {
- if (!buckets[i]->first_node) {
- char *ret = malloc(qhead->prefixlen + 2);
- if (!ret) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- memcpy(ret, qhead->first_node->string, qhead->prefixlen);
- ret[qhead->prefixlen] = i + 'A';
- ret[qhead->prefixlen + 1] = '\0';
- /* This would be where we freed everything, if we
- * didn't know it didn't matter because we were
- * imminently going to exec another program */
- return ret;
- }
- }
- qhead = qhead->next_bucket;
- }
- }
- /* ----------------------------------------------------------------------
- * Get the pathname of this executable, so we can locate the rest of
- * the app bundle relative to it.
- */
- /*
- * There are several ways to try to retrieve the pathname to the
- * running executable:
- *
- * (a) Declare main() as taking four arguments int main(int argc, char
- * **argv, char **envp, char **apple); and look at apple[0].
- *
- * (b) Use sysctl(KERN_PROCARGS) to get the process arguments for the
- * current pid. This involves two steps:
- * - sysctl(mib, 2, &argmax, &argmax_size, NULL, 0)
- * + mib is an array[2] of int containing
- * { CTL_KERN, KERN_ARGMAX }
- * + argmax is an int
- * + argmax_size is a size_t initialised to sizeof(argmax)
- * + returns in argmax the amount of memory you need for the next
- * call.
- * - sysctl(mib, 3, procargs, &procargs_size, NULL, 0)
- * + mib is an array[3] of int containing
- * { CTL_KERN, KERN_PROCARGS, current pid }
- * + procargs is a buffer of size 'argmax'
- * + procargs_size is a size_t initialised to argmax
- * + returns in the procargs buffer a collection of
- * zero-terminated strings of which the first is the program
- * name.
- *
- * (c) Call _NSGetExecutablePath, once to find out the needed buffer
- * size and again to fetch the actual path.
- *
- * (d) Use Objective-C and Cocoa and call
- * [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0].
- *
- * So, how do those work in various cases? Experiments show:
- *
- * - if you run the program as 'binary' (or whatever you called it)
- * and rely on the shell to search your PATH, all four methods
- * return a sensible-looking absolute pathname.
- *
- * - if you run the program as './binary', (a) and (b) return just
- * "./binary", which has a particularly bad race condition if you
- * try to convert it into an absolute pathname using realpath(3).
- * (c) returns "/full/path/to/./binary", which still needs
- * realpath(3)ing to get rid of that ".", but at least it's
- * _trying_ to be fully qualified. (d) returns
- * "/full/path/to/binary" - full marks!
- * + Similar applies if you run it via a more interesting relative
- * path such as one with a ".." in: (c) gives you an absolute
- * path containing a ".." element, whereas (d) has sorted that
- * out.
- *
- * - if you run the program via a path with a symlink on, _none_ of
- * these options successfully returns a path without the symlink.
- *
- * That last point suggests that even (d) is not a perfect solution on
- * its own, and you'll have to realpath() whatever you get back from
- * it regardless.
- *
- * And (d) is extra inconvenient because it returns an NSString, which
- * is implicitly Unicode, so it's not clear how you turn that back
- * into a char * representing a correct Unix pathname (what charset
- * should you interpret it in?). Also because you have to bring in all
- * of ObjC and Cocoa, which for a low-level Unix API client like this
- * seems like overkill.
- *
- * So my conclusion is that (c) is most practical for these purposes.
- */
- char *get_program_path(void)
- {
- char *our_path;
- uint32_t pathlen = 0;
- _NSGetExecutablePath(NULL, &pathlen);
- our_path = malloc(pathlen);
- if (!our_path) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- if (_NSGetExecutablePath(our_path, &pathlen)) {
- fprintf(stderr, "unable to get launcher executable path\n");
- exit(1);
- }
- /* OS X guarantees to malloc the return value if we pass NULL */
- char *our_real_path = realpath(our_path, NULL);
- if (!our_real_path) {
- fprintf(stderr, "realpath failed\n");
- exit(1);
- }
- free(our_path);
- return our_real_path;
- }
- /* ----------------------------------------------------------------------
- * Wrapper on dirname(3) which mallocs its return value to whatever
- * size is needed.
- */
- char *dirname_wrapper(const char *path)
- {
- char *path_copy = malloc(strlen(path) + 1);
- if (!path_copy) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- strcpy(path_copy, path);
- char *ret_orig = dirname(path_copy);
- char *ret = malloc(strlen(ret_orig) + 1);
- if (!ret) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- strcpy(ret, ret_orig);
- free(path_copy);
- return ret;
- }
- /* ----------------------------------------------------------------------
- * mallocing string concatenation function.
- */
- char *alloc_cat(const char *str1, const char *str2)
- {
- int len1 = strlen(str1), len2 = strlen(str2);
- char *ret = malloc(len1 + len2 + 1);
- if (!ret) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- strcpy(ret, str1);
- strcpy(ret + len1, str2);
- return ret;
- }
- /* ----------------------------------------------------------------------
- * Overwrite an environment variable, preserving the old one for the
- * real app to restore.
- */
- void setenv_wrap(const char *name, const char *value)
- {
- #ifdef DEBUG_OSXLAUNCH
- printf("setenv(\"%s\",\"%s\")\n", name, value);
- #endif
- setenv(name, value, 1);
- }
- void unsetenv_wrap(const char *name)
- {
- #ifdef DEBUG_OSXLAUNCH
- printf("unsetenv(\"%s\")\n", name);
- #endif
- unsetenv(name);
- }
- char *prefix, *prefixset, *prefixunset;
- void overwrite_env(const char *name, const char *value)
- {
- const char *oldvalue = getenv(name);
- if (oldvalue) {
- setenv_wrap(alloc_cat(prefixset, name), oldvalue);
- } else {
- setenv_wrap(alloc_cat(prefixunset, name), "");
- }
- if (value)
- setenv_wrap(name, value);
- else
- unsetenv_wrap(name);
- }
- /* ----------------------------------------------------------------------
- * Main program.
- */
- int main(int argc, char **argv)
- {
- prefix = get_unused_env_prefix();
- prefixset = alloc_cat(prefix, "s");
- prefixunset = alloc_cat(prefix, "u");
- #ifdef DEBUG_OSXLAUNCH
- printf("Environment prefixes: main=\"%s\", set=\"%s\", unset=\"%s\"\n",
- prefix, prefixset, prefixunset);
- #endif
- char *prog_path = get_program_path(); // <bundle>/Contents/MacOS/<filename>
- char *macos = dirname_wrapper(prog_path); // <bundle>/Contents/MacOS
- char *contents = dirname_wrapper(macos); // <bundle>/Contents
- // char *bundle = dirname_wrapper(contents); // <bundle>
- char *resources = alloc_cat(contents, "/Resources");
- // char *bin = alloc_cat(resources, "/bin");
- char *etc = alloc_cat(resources, "/etc");
- char *lib = alloc_cat(resources, "/lib");
- char *share = alloc_cat(resources, "/share");
- char *xdg = alloc_cat(etc, "/xdg");
- // char *gtkrc = alloc_cat(etc, "/gtk-2.0/gtkrc");
- char *locale = alloc_cat(share, "/locale");
- char *realbin = alloc_cat(prog_path, "-bin");
- // overwrite_env("DYLD_LIBRARY_PATH", lib);
- overwrite_env("XDG_CONFIG_DIRS", xdg);
- overwrite_env("XDG_DATA_DIRS", share);
- overwrite_env("GTK_DATA_PREFIX", resources);
- overwrite_env("GTK_EXE_PREFIX", resources);
- overwrite_env("GTK_PATH", resources);
- overwrite_env("PANGO_LIBDIR", lib);
- overwrite_env("PANGO_SYSCONFDIR", etc);
- overwrite_env("I18NDIR", locale);
- overwrite_env("LANG", NULL);
- overwrite_env("LC_MESSAGES", NULL);
- overwrite_env("LC_MONETARY", NULL);
- overwrite_env("LC_COLLATE", NULL);
- char **new_argv = malloc((argc + 16) * sizeof(const char *));
- if (!new_argv) {
- fprintf(stderr, "out of memory\n");
- exit(1);
- }
- int j = 0;
- new_argv[j++] = realbin;
- #ifdef DEBUG_OSXLAUNCH
- printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]);
- #endif
- {
- int i = 1;
- if (i < argc && !strncmp(argv[i], "-psn_", 5))
- i++;
- for (; i < argc; i++) {
- new_argv[j++] = argv[i];
- #ifdef DEBUG_OSXLAUNCH
- printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]);
- #endif
- }
- }
- new_argv[j++] = prefix;
- #ifdef DEBUG_OSXLAUNCH
- printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]);
- #endif
- new_argv[j++] = NULL;
- #ifdef DEBUG_OSXLAUNCH
- printf("executing \"%s\"\n", realbin);
- #endif
- execv(realbin, new_argv);
- perror("execv");
- free(new_argv);
- free(contents);
- free(macos);
- return 127;
- }
- #endif /* __APPLE__ */
|