зеркало из https://github.com/github/putty.git
472 строки
15 KiB
C
472 строки
15 KiB
C
/*
|
|
* 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__ */
|