[mkbundle] Don't free bundled dylibrary directory (#3897)
[mono.git] / mono / mini / main.c
1 /*
2  * main.c: The main entry point for the mono executable
3  *
4  * The main entry point does a few things:
5  * 
6  *   * It probes whether the executable has a bundle appended
7  *     at the end, and if so, registers the various bundled
8  *     resources with Mono and executes the contained bundle
9  *
10  *   * Parses the MONO_ENV_OPTIONS variable to treat the
11  *     contents of the variable as command line arguments for
12  *     the mono runtime
13  *
14  *   * Launches Mono, by calling mono_main.
15  */
16 #include <config.h>
17 #include <fcntl.h>
18 #include <mono/metadata/assembly.h>
19 #include <mono/metadata/mono-config.h>
20 #include <mono/utils/mono-mmap.h>
21 #include <mono/utils/mono-dl.h>
22 #include "mini.h"
23
24 #ifdef HAVE_UNISTD_H
25 #  include <unistd.h>
26 #endif
27 #ifdef HOST_WIN32
28 #  include <io.h>
29 #else
30 #  ifndef BUILDVER_INCLUDED
31 #    include "buildver-boehm.h"
32 #  endif
33 #endif
34
35 /*
36  * If the MONO_ENV_OPTIONS environment variable is set, it uses this as a
37  * source of command line arguments that are passed to Mono before the
38  * command line arguments specified in the command line.
39  */
40 static int
41 mono_main_with_options (int argc, char *argv [])
42 {
43         mono_parse_env_options (&argc, &argv);
44
45         return mono_main (argc, argv);
46 }
47
48 /*
49  * The Mono executable can initialize itself from a payload attached
50  * at the end of the main program.   The payload contains the
51  * main assembly, one or more managed assemblies, configuration
52  * files and other assets that are used instead of launching a
53  * program from the command line.
54  *
55  * The startup sequence probes for a magical signature at the end of
56  * the executable, if the 16 characters "xmonkeysloveplay" are found,
57  * the code expects the 64-bits just before it to contain an offset
58  * within the executable with a directory of assets.
59  *
60  * All pointers in the file format are encoded as little-endian values
61  *
62  * The format of the file is thus:
63  *
64  * Location        Content
65  * --------        -------
66  * lenght-16       Optional "xmonkeysloveplay", indicating that a
67  *                 bundled payload is contained in the executable.
68  * length-24       pointer to the directory in the file, address DIR
69  *
70  * DIR             32-bit value with the number of entries in the directory
71  * DIR+4           First directory entry.
72  *
73  * Each directory entry is made up of:
74  * 4-bytes         uint32_t containing the size of a string (STR)
75  * STRING          UTF8 encoded and \0 terminated string
76  * 8-bytes         uint64_t offset in the file with the payload associated with STRING
77  * 4-bytes         uint32_t size of the asset
78  *
79  * The following are the known directory entries, without the quotes:
80  * "assembly:NAME"  An assembly with the name NAME, assembly is in the payload
81  * "config:NAME"    A configuration file (usually file.dll.config) in the payload that is
82  *                  loaded as the config file for an assembly
83  * "systemconfig:"  Treats as a Mono system configuration, payload contains the config file.
84  * "options:"       The payload contains command line options to initialize Mono, as if you 
85                     had set them on MONO_ENV_OPTIONS
86  * "config_dir:DIR" Configures the MONO_PATH to point to point to DIR
87  * "machineconfig:" The payload contains the machine.config file to use at runtime
88  * "env:"           Sets the environment variable to the value encoded in the payload
89  *                  payload contains: 1-byte lenght for the \0 terminated variable,
90  *                  followed by the value.
91  * "library:NAME"   Bundled dynamic library NAME, payload contains the dynamic library
92  */
93 #define STREAM_INT(x) GUINT32_TO_LE((*(uint32_t*)x))
94 #define STREAM_LONG(x) GUINT64_TO_LE((*(uint64_t*)x))
95
96 /**
97  * Loads a chunk of data from the file pointed to by the
98  * @fd starting at the file offset @offset for @size bytes
99  * and returns an allocated version of that string, or NULL
100  * on error.
101  */
102 static char *
103 load_from_region (int fd, uint64_t offset, uint64_t size)
104 {
105         char *buffer;
106         off_t loc;
107         int status;
108         
109         do {
110                 loc = lseek (fd, offset, SEEK_SET);
111         } while (loc == -1 && errno == EINTR);
112         if (loc == -1)
113                 return NULL;
114         buffer = g_malloc (size + 1);
115         if (buffer == NULL)
116                 return NULL;
117         buffer [size] = 0;
118         do {
119                 status = read (fd, buffer, size);
120         } while (status == -1 && errno == EINTR);
121         if (status == -1){
122                 g_free (buffer);
123                 return NULL;
124         }
125         return buffer;
126 }
127
128 /* Did we initialize the temporary directory for dynamic libraries */
129 static int bundle_save_library_initialized;
130
131 /* List of bundled libraries we unpacked */
132 static GSList *bundle_library_paths;
133
134 /* Directory where we unpacked dynamic libraries */
135 static char *bundled_dylibrary_directory;
136
137 static void
138 delete_bundled_libraries ()
139 {
140         GSList *list;
141
142         for (list = bundle_library_paths; list != NULL; list = list->next){
143                 unlink (list->data);
144         }
145         rmdir (bundled_dylibrary_directory);
146 }
147
148 static void
149 bundle_save_library_initialize ()
150 {
151         bundle_save_library_initialized = 1;
152         char *path = g_build_filename (g_get_tmp_dir (), "mono-bundle-XXXXXX", NULL);
153         bundled_dylibrary_directory = g_mkdtemp (path);
154         /* don't free path - mkdtemp modifies it in place, and bundled_dylibrary_directory is an alias of it */
155         if (bundled_dylibrary_directory == NULL)
156                 return;
157         atexit (delete_bundled_libraries);
158 }
159
160 static void
161 save_library (int fd, uint64_t offset, uint64_t size, const char *destfname)
162 {
163         MonoDl *lib;
164         char *file, *buffer, *err, *internal_path;
165         if (!bundle_save_library_initialized)
166                 bundle_save_library_initialize ();
167         
168         file = g_build_filename (bundled_dylibrary_directory, destfname, NULL);
169         buffer = load_from_region (fd, offset, size);
170         g_file_set_contents (file, buffer, size, NULL);
171
172         lib = mono_dl_open (file, MONO_DL_LAZY, &err);
173         if (lib == NULL){
174                 fprintf (stderr, "Error loading shared library: %s %s\n", file, err);
175                 exit (1);
176         }
177         // Register the name with "." as this is how it will be found when embedded
178         internal_path = g_build_filename (".", destfname, NULL);
179         mono_loader_register_module (internal_path, lib);
180         g_free (internal_path);
181         bundle_library_paths = g_slist_append (bundle_library_paths, file);
182         
183         g_free (buffer);
184 }
185
186 static gboolean
187 probe_embedded (const char *program, int *ref_argc, char **ref_argv [])
188 {
189         MonoBundledAssembly last = { NULL, 0, 0 };
190         char sigbuffer [16+sizeof (uint64_t)];
191         gboolean status = FALSE;
192         uint64_t directory_location;
193         off_t sigstart, baseline = 0;
194         uint64_t directory_size;
195         char *directory, *p;
196         int items, i;
197         unsigned char *mapaddress = NULL;
198         void *maphandle = NULL;
199         GArray *assemblies;
200         char *entry_point = NULL;
201         char **new_argv;
202         int j;
203
204         int fd = open (program, O_RDONLY);
205         if (fd == -1)
206                 return FALSE;
207         if ((sigstart = lseek (fd, -24, SEEK_END)) == -1)
208                 goto doclose;
209         if (read (fd, sigbuffer, sizeof (sigbuffer)) == -1)
210                 goto doclose;
211         if (memcmp (sigbuffer+sizeof(uint64_t), "xmonkeysloveplay", 16) != 0)
212                 goto doclose;
213         directory_location = GUINT64_FROM_LE ((*(uint64_t *) &sigbuffer [0]));
214         if (lseek (fd, directory_location, SEEK_SET) == -1)
215                 goto doclose;
216         directory_size = sigstart-directory_location;
217         directory = g_malloc (directory_size);
218         if (directory == NULL)
219                 goto doclose;
220         if (read (fd, directory, directory_size) == -1)
221                 goto dofree;
222
223         items = STREAM_INT (directory);
224         p = directory+4;
225
226         assemblies = g_array_new (0, 0, sizeof (MonoBundledAssembly*));
227         for (i = 0; i < items; i++){
228                 char *kind;
229                 int strsize = STREAM_INT (p);
230                 uint64_t offset, item_size;
231                 kind = p+4;
232                 p += 4 + strsize;
233                 offset = STREAM_LONG(p);
234                 p += 8;
235                 item_size = STREAM_INT (p);
236                 p += 4;
237                 
238                 if (mapaddress == NULL){
239                         mapaddress = mono_file_map (directory_location-offset, MONO_MMAP_READ | MONO_MMAP_PRIVATE, fd, offset, &maphandle);
240                         if (mapaddress == NULL){
241                                 perror ("Error mapping file");
242                                 exit (1);
243                         }
244                         baseline = offset;
245                 }
246                 if (strncmp (kind, "assembly:", strlen ("assembly:")) == 0){
247                         char *aname = kind + strlen ("assembly:");
248                         MonoBundledAssembly mba = { aname, mapaddress + offset - baseline, item_size }, *ptr;
249                         ptr = g_new (MonoBundledAssembly, 1);
250                         memcpy (ptr, &mba, sizeof (MonoBundledAssembly));
251                         g_array_append_val  (assemblies, ptr);
252                         if (entry_point == NULL)
253                                 entry_point = aname;
254                 } else if (strncmp (kind, "config:", strlen ("config:")) == 0){
255                         char *config = kind + strlen ("config:");
256                         char *aname = g_strdup (config);
257                         aname [strlen(aname)-strlen(".config")] = 0;
258                         mono_register_config_for_assembly (aname, load_from_region (fd, offset, item_size));
259                 } else if (strncmp (kind, "systemconfig:", strlen ("systemconfig:")) == 0){
260                         mono_config_parse_memory (load_from_region (fd, offset, item_size));
261                 } else if (strncmp (kind, "options:", strlen ("options:")) == 0){
262                         mono_parse_options_from (load_from_region (fd, offset, item_size), ref_argc, ref_argv);
263                 } else if (strncmp (kind, "config_dir:", strlen ("config_dir:")) == 0){
264                         mono_set_dirs (getenv ("MONO_PATH"), load_from_region (fd, offset, item_size));
265                 } else if (strncmp (kind, "machineconfig:", strlen ("machineconfig:")) == 0) {
266                         mono_register_machine_config (load_from_region (fd, offset, item_size));
267                 } else if (strncmp (kind, "env:", strlen ("env:")) == 0){
268                         char *data = load_from_region (fd, offset, item_size);
269                         uint8_t count = *data++;
270                         char *value = data + count + 1;
271                         g_setenv (data, value, FALSE);
272                 } else if (strncmp (kind, "library:", strlen ("library:")) == 0){
273                         save_library (fd, offset, item_size, kind + strlen ("library:"));
274                 } else {
275                         fprintf (stderr, "Unknown stream on embedded package: %s\n", kind);
276                         exit (1);
277                 }
278         }
279         g_array_append_val (assemblies, last);
280
281         mono_register_bundled_assemblies ((const MonoBundledAssembly **) assemblies->data);
282         new_argv = g_new (char *, (*ref_argc)+1);
283         new_argv [0] = (*ref_argv)[0];
284         new_argv [1] = entry_point;
285         for (j = 1; j < *ref_argc; j++)
286                 new_argv [j+1] = (*ref_argv)[j];
287         *ref_argv = new_argv;
288         (*ref_argc)++;
289         
290         return TRUE;
291         
292 dofree:
293         g_free (directory);
294 doclose:
295         if (!status)
296                 close (fd);
297         return status;
298 }
299
300 #ifdef HOST_WIN32
301
302 #include <shellapi.h>
303
304 int
305 main (void)
306 {
307         TCHAR szFileName[MAX_PATH];
308         int argc;
309         gunichar2** argvw;
310         gchar** argv;
311         int i;
312         DWORD count;
313         
314         argvw = CommandLineToArgvW (GetCommandLine (), &argc);
315         argv = g_new0 (gchar*, argc + 1);
316         for (i = 0; i < argc; i++)
317                 argv [i] = g_utf16_to_utf8 (argvw [i], -1, NULL, NULL, NULL);
318         argv [argc] = NULL;
319
320         LocalFree (argvw);
321
322         if ((count = GetModuleFileName (NULL, szFileName, MAX_PATH)) != 0){
323                 char *entry = g_utf16_to_utf8 (szFileName, count, NULL, NULL, NULL);
324                 probe_embedded (entry, &argc, &argv);
325         }
326
327         return mono_main_with_options  (argc, argv);
328 }
329
330 #else
331
332 int
333 main (int argc, char* argv[])
334 {
335         mono_build_date = build_date;
336
337         probe_embedded (argv [0], &argc, &argv);
338         return mono_main_with_options (argc, argv);
339 }
340
341 #endif