1 /* 2 * This wrapper program executes a python executable hidden inside an 3 * application bundle inside the Python framework. This is needed to run 4 * GUI code: some GUI API's don't work unless the program is inside an 5 * application bundle. 6 * 7 * This program uses posix_spawn rather than plain execv because we need 8 * slightly more control over how the "real" interpreter is executed. 9 * 10 * On OSX 10.4 (and earlier) this falls back to using exec because the 11 * posix_spawnv functions aren't available there. 12 */ 13 14 #pragma weak_import posix_spawnattr_init 15 #pragma weak_import posix_spawnattr_setbinpref_np 16 #pragma weak_import posix_spawnattr_setflags 17 #pragma weak_import posix_spawn 18 19 #include <Python.h> 20 #include <unistd.h> 21 #ifdef HAVE_SPAWN_H 22 #include <spawn.h> 23 #endif 24 #include <stdio.h> 25 #include <string.h> 26 #include <errno.h> 27 #include <err.h> 28 #include <dlfcn.h> 29 #include <stdlib.h> 30 #include <Python.h> 31 #include <mach-o/dyld.h> 32 33 34 extern char** environ; 35 36 /* 37 * Locate the python framework by looking for the 38 * library that contains Py_Initialize. 39 * 40 * In a regular framework the structure is: 41 * 42 * Python.framework/Versions/2.7 43 * /Python 44 * /Resources/Python.app/Contents/MacOS/Python 45 * 46 * In a virtualenv style structure the expected 47 * structure is: 48 * 49 * ROOT 50 * /bin/pythonw 51 * /.Python <- the dylib 52 * /.Resources/Python.app/Contents/MacOS/Python 53 * 54 * NOTE: virtualenv's are not an officially supported 55 * feature, support for that structure is provided as 56 * a convenience. 57 */ 58 static char* get_python_path(void) 59 { 60 size_t len; 61 Dl_info info; 62 char* end; 63 char* g_path; 64 65 if (dladdr(Py_Initialize, &info) == 0) { 66 return NULL; 67 } 68 69 len = strlen(info.dli_fname); 70 71 g_path = malloc(len+60); 72 if (g_path == NULL) { 73 return NULL; 74 } 75 76 strcpy(g_path, info.dli_fname); 77 end = g_path + len - 1; 78 while (end != g_path && *end != '/') { 79 end --; 80 } 81 end++; 82 if (*end == '.') { 83 end++; 84 } 85 strcpy(end, "Resources/Python.app/Contents/MacOS/" PYTHONFRAMEWORK); 86 87 return g_path; 88 } 89 90 #ifdef HAVE_SPAWN_H 91 static void 92 setup_spawnattr(posix_spawnattr_t* spawnattr) 93 { 94 size_t ocount; 95 size_t count; 96 cpu_type_t cpu_types[1]; 97 short flags = 0; 98 #ifdef __LP64__ 99 int ch; 100 #endif 101 102 if ((errno = posix_spawnattr_init(spawnattr)) != 0) { 103 err(2, "posix_spawnattr_int"); 104 /* NOTREACHTED */ 105 } 106 107 count = 1; 108 109 /* Run the real python executable using the same architecture as this 110 * executable, this allows users to control the architecture using 111 * "arch -ppc python" 112 */ 113 114 #if defined(__ppc64__) 115 cpu_types[0] = CPU_TYPE_POWERPC64; 116 117 #elif defined(__x86_64__) 118 cpu_types[0] = CPU_TYPE_X86_64; 119 120 #elif defined(__ppc__) 121 cpu_types[0] = CPU_TYPE_POWERPC; 122 #elif defined(__i386__) 123 cpu_types[0] = CPU_TYPE_X86; 124 #else 125 # error "Unknown CPU" 126 #endif 127 128 if (posix_spawnattr_setbinpref_np(spawnattr, count, 129 cpu_types, &ocount) == -1) { 130 err(1, "posix_spawnattr_setbinpref"); 131 /* NOTREACHTED */ 132 } 133 if (count != ocount) { 134 fprintf(stderr, "posix_spawnattr_setbinpref failed to copy\n"); 135 exit(1); 136 /* NOTREACHTED */ 137 } 138 139 140 /* 141 * Set flag that causes posix_spawn to behave like execv 142 */ 143 flags |= POSIX_SPAWN_SETEXEC; 144 if ((errno = posix_spawnattr_setflags(spawnattr, flags)) != 0) { 145 err(1, "posix_spawnattr_setflags"); 146 /* NOTREACHTED */ 147 } 148 } 149 #endif 150 151 int 152 main(int argc, char **argv) { 153 char* exec_path = get_python_path(); 154 static char path[PATH_MAX * 2]; 155 static char real_path[PATH_MAX * 2]; 156 int status; 157 uint32_t size = PATH_MAX * 2; 158 159 /* Set the original executable path in the environment. */ 160 status = _NSGetExecutablePath(path, &size); 161 if (status == 0) { 162 /* 163 * Note: don't call 'realpath', that will 164 * erase symlink information, and that 165 * breaks "pyvenv --symlink" 166 * 167 * It is nice to have the directory name 168 * as a cleaned up absolute path though, 169 * therefore call realpath on dirname(path) 170 */ 171 char* slash = strrchr(path, '/'); 172 if (slash) { 173 char replaced; 174 replaced = slash[1]; 175 slash[1] = 0; 176 if (realpath(path, real_path) == NULL) { 177 err(1, "realpath: %s", path); 178 } 179 slash[1] = replaced; 180 if (strlcat(real_path, slash, sizeof(real_path)) > sizeof(real_path)) { 181 errno = EINVAL; 182 err(1, "realpath: %s", path); 183 } 184 185 } else { 186 if (realpath(".", real_path) == NULL) { 187 err(1, "realpath: %s", path); 188 } 189 if (strlcat(real_path, "/", sizeof(real_path)) > sizeof(real_path)) { 190 errno = EINVAL; 191 err(1, "realpath: %s", path); 192 } 193 if (strlcat(real_path, path, sizeof(real_path)) > sizeof(real_path)) { 194 errno = EINVAL; 195 err(1, "realpath: %s", path); 196 } 197 } 198 199 setenv("__PYVENV_LAUNCHER__", real_path, 1); 200 } 201 202 /* 203 * Let argv[0] refer to the new interpreter. This is needed to 204 * get the effect we want on OSX 10.5 or earlier. That is, without 205 * changing argv[0] the real interpreter won't have access to 206 * the Window Server. 207 */ 208 argv[0] = exec_path; 209 210 #ifdef HAVE_SPAWN_H 211 /* We're weak-linking to posix-spawnv to ensure that 212 * an executable build on 10.5 can work on 10.4. 213 */ 214 if (posix_spawn != NULL) { 215 posix_spawnattr_t spawnattr = NULL; 216 217 setup_spawnattr(&spawnattr); 218 posix_spawn(NULL, exec_path, NULL, 219 &spawnattr, argv, environ); 220 err(1, "posix_spawn: %s", exec_path); 221 } 222 #endif 223 execve(exec_path, argv, environ); 224 err(1, "execve: %s", argv[0]); 225 /* NOTREACHED */ 226 } 227