xref: /haiku/src/system/libroot/os/image.cpp (revision 1a3518cf757c2da8006753f83962da5935bbc82b)
1 /*
2  * Copyright 2003-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <image.h>
7 #include <image_private.h>
8 
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #include <algorithm>
13 #include <new>
14 
15 #include <fs_attr.h>
16 
17 #include <AutoDeleter.h>
18 
19 #include <libroot_private.h>
20 #include <runtime_loader.h>
21 #include <syscalls.h>
22 #include <syscall_load_image.h>
23 #include <user_runtime.h>
24 
25 
26 struct EnvironmentFilter {
27 	EnvironmentFilter()
28 		:
29 		fBuffer(NULL),
30 		fEntries(NULL),
31 		fBufferSize(0),
32 		fEntryCount(0),
33 		fAdditionalEnvCount(0),
34 		fNextEntryIndex(0)
35 	{
36 	}
37 
38 	~EnvironmentFilter()
39 	{
40 		free(fBuffer);
41 		delete[] fEntries;
42 	}
43 
44 	void Init(const char* path, const char* const* env, size_t envCount)
45 	{
46 		int fd = open(path, O_RDONLY);
47 		if (fd < 0)
48 			return;
49 		FileDescriptorCloser fdCloser(fd);
50 
51 		static const char* const kEnvAttribute = "SYS:ENV";
52 		attr_info info;
53 		if (fs_stat_attr(fd, kEnvAttribute, &info) < 0)
54 			return;
55 
56 		_Init(fd, kEnvAttribute, info.size, env, envCount);
57 	}
58 
59 	size_t AdditionalSlotsNeeded() const
60 	{
61 		return fAdditionalEnvCount;
62 	}
63 
64 	size_t AdditionalSizeNeeded() const
65 	{
66 		return fBufferSize + fAdditionalEnvCount * sizeof(char*);
67 	}
68 
69 	size_t PrepareSlot(const char* env, int32 index, char* buffer)
70 	{
71 		if (fNextEntryIndex < fEntryCount
72 			&& fEntries[fNextEntryIndex].index == index) {
73 			env = fEntries[fNextEntryIndex].replacement;
74 			fNextEntryIndex++;
75 		}
76 
77 		return _FillSlot(env, buffer);
78 	}
79 
80 	void PrepareAdditionalSlots(char**& slot, char*& buffer)
81 	{
82 		for (size_t i = 0; i < fAdditionalEnvCount; i++) {
83 			size_t envSize = _FillSlot(fEntries[i].replacement, buffer);
84 			*slot++ = buffer;
85 			buffer += envSize;
86 		}
87 	}
88 
89 private:
90 	void _Init(int fd, const char* attribute, size_t size,
91 		const char* const* env, size_t envCount)
92 	{
93 		if (size == 0)
94 			return;
95 
96 		// read the attribute
97 		char* buffer = (char*)malloc(size + 1);
98 		if (buffer == NULL)
99 			return;
100 		MemoryDeleter bufferDeleter(buffer);
101 
102 		ssize_t bytesRead = fs_read_attr(fd, attribute, B_STRING_TYPE, 0,
103 			buffer, size);
104 		if (bytesRead < 0 || (size_t)bytesRead != size)
105 			return;
106 		buffer[size] = '\0';
107 
108 		// deescape the buffer and count the entries
109 		size_t entryCount = 1;
110 		char* out = buffer;
111 		for (const char* c = buffer; *c != '\0'; c++) {
112 			if (*c == '\\') {
113 				c++;
114 				if (*c == '\0')
115 					break;
116 				if (*c == '0') {
117 					*out++ = '\0';
118 					entryCount++;
119 				} else
120 					*out++ = *c;
121 			} else
122 				*out++ = *c;
123 		}
124 		*out++ = '\0';
125 		size = out - buffer + 1;
126 
127 		// create an entry array
128 		fEntries = new(std::nothrow) Entry[entryCount];
129 		if (fEntries == NULL)
130 			return;
131 
132 		bufferDeleter.Detach();
133 		fBuffer = buffer;
134 		fBufferSize = size;
135 
136 		// init the entries
137 		out = buffer;
138 		for (size_t i = 0; i < entryCount; i++) {
139 			const char* separator = strchr(out, '=');
140 			if (separator != NULL && separator != out) {
141 				fEntries[fEntryCount].replacement = out;
142 				fEntries[fEntryCount].index = _FindEnvEntry(env, envCount, out,
143 					separator - out);
144 				if (fEntries[fEntryCount].index < 0)
145 					fAdditionalEnvCount++;
146 				fEntryCount++;
147 			}
148 			out += strlen(out) + 1;
149 		}
150 
151 		if (fEntryCount > 1)
152 			std::sort(fEntries, fEntries + fEntryCount);
153 
154 		// Advance fNextEntryIndex to the first entry pointing to an existing
155 		// env variable.
156 		while (fNextEntryIndex < fEntryCount
157 			&& fEntries[fNextEntryIndex].index < 0) {
158 			fNextEntryIndex++;
159 		}
160 	}
161 
162 	int32 _FindEnvEntry(const char* const* env, size_t envCount,
163 		const char* variable, size_t variableLength)
164 	{
165 		for (size_t i = 0; i < envCount; i++) {
166 			if (strncmp(env[i], variable, variableLength) == 0
167 				&& env[i][variableLength] == '=') {
168 				return i;
169 			}
170 		}
171 
172 		return -1;
173 	}
174 
175 	size_t _FillSlot(const char* env, char* buffer)
176 	{
177 		size_t envSize = strlen(env) + 1;
178 		memcpy(buffer, env, envSize);
179 		return envSize;
180 	}
181 
182 private:
183 	struct Entry {
184 		char*	replacement;
185 		int32	index;
186 
187 		bool operator<(const Entry& other) const
188 		{
189 			return index < other.index;
190 		}
191 	};
192 
193 private:
194 	char*	fBuffer;
195 	Entry*	fEntries;
196 	size_t	fBufferSize;
197 	size_t	fEntryCount;
198 	size_t	fAdditionalEnvCount;
199 	size_t	fNextEntryIndex;
200 };
201 
202 
203 thread_id
204 __load_image_at_path(const char* path, int32 argCount, const char **args,
205 	const char **environ)
206 {
207 	char invoker[B_FILE_NAME_LENGTH];
208 	char **newArgs = NULL;
209 	int32 envCount = 0;
210 	thread_id thread;
211 
212 	if (argCount < 1 || environ == NULL)
213 		return B_BAD_VALUE;
214 
215 	// test validity of executable + support for scripts
216 	{
217 		status_t status = __test_executable(path, invoker);
218 		if (status < B_OK)
219 			return status;
220 
221 		if (invoker[0]) {
222 			status = __parse_invoke_line(invoker, &newArgs,
223 				(char * const **)&args, &argCount, path);
224 			if (status < B_OK)
225 				return status;
226 		}
227 	}
228 
229 	// count environment variables
230 	while (environ[envCount] != NULL)
231 		envCount++;
232 
233 	char** flatArgs = NULL;
234 	size_t flatArgsSize;
235 	status_t status = __flatten_process_args(args, argCount, environ,
236 		&envCount, path, &flatArgs, &flatArgsSize);
237 
238 	if (status == B_OK) {
239 		thread = _kern_load_image(flatArgs, flatArgsSize, argCount, envCount,
240 			B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, -1, 0);
241 
242 		free(flatArgs);
243 	} else
244 		thread = status;
245 
246 	free(newArgs);
247 	return thread;
248 }
249 
250 
251 thread_id
252 load_image(int32 argCount, const char **args, const char **environ)
253 {
254 	return __load_image_at_path(args[0], argCount, args, environ);
255 }
256 
257 
258 image_id
259 load_add_on(char const *name)
260 {
261 	if (name == NULL)
262 		return B_BAD_VALUE;
263 
264 	return __gRuntimeLoader->load_add_on(name, 0);
265 }
266 
267 
268 status_t
269 unload_add_on(image_id id)
270 {
271 	return __gRuntimeLoader->unload_add_on(id);
272 }
273 
274 
275 status_t
276 get_image_symbol(image_id id, char const *symbolName, int32 symbolType,
277 	void **_location)
278 {
279 	return __gRuntimeLoader->get_image_symbol(id, symbolName, symbolType,
280 		false, NULL, _location);
281 }
282 
283 
284 status_t
285 get_image_symbol_etc(image_id id, char const *symbolName, int32 symbolType,
286 	bool recursive, image_id *_inImage, void **_location)
287 {
288 	return __gRuntimeLoader->get_image_symbol(id, symbolName, symbolType,
289 		recursive, _inImage, _location);
290 }
291 
292 
293 status_t
294 get_nth_image_symbol(image_id id, int32 num, char *nameBuffer, int32 *_nameLength,
295 	int32 *_symbolType, void **_location)
296 {
297 	return __gRuntimeLoader->get_nth_image_symbol(id, num, nameBuffer, _nameLength, _symbolType, _location);
298 }
299 
300 
301 status_t
302 _get_image_info(image_id id, image_info *info, size_t infoSize)
303 {
304 	return _kern_get_image_info(id, info, infoSize);
305 }
306 
307 
308 status_t
309 _get_next_image_info(team_id team, int32 *cookie, image_info *info, size_t infoSize)
310 {
311 	return _kern_get_next_image_info(team, cookie, info, infoSize);
312 }
313 
314 
315 void
316 clear_caches(void *address, size_t length, uint32 flags)
317 {
318 	_kern_clear_caches(address, length, flags);
319 }
320 
321 
322 //	#pragma mark -
323 
324 
325 status_t
326 __look_up_in_path(const char *file, char *buffer)
327 {
328 	// get the PATH
329 	const char* paths = getenv("PATH");
330 	if (paths == NULL)
331 		return B_ENTRY_NOT_FOUND;
332 
333 	int fileNameLen = strlen(file);
334 
335 	// iterate through the paths
336 	const char* pathEnd = paths - 1;
337 	while (pathEnd != NULL) {
338 		paths = pathEnd + 1;
339 		pathEnd = strchr(paths, ':');
340 		int pathLen = (pathEnd ? pathEnd - paths : strlen(paths));
341 
342 		// We skip empty paths and those that would become too long.
343 		// The latter is not really correct, but practically irrelevant.
344 		if (pathLen == 0
345 			|| pathLen + 1 + fileNameLen >= B_PATH_NAME_LENGTH) {
346 			continue;
347 		}
348 
349 		// concatinate the program path
350 		char path[B_PATH_NAME_LENGTH];
351 		memcpy(path, paths, pathLen);
352 		path[pathLen] = '\0';
353 
354 		if (path[pathLen - 1] != '/')
355 			strcat(path, "/");
356 		strcat(path, file);
357 
358 		// check whether it is a file
359 		struct stat st;
360 		if (stat(path, &st) != 0 || !S_ISREG(st.st_mode))
361 			continue;
362 
363 		// if executable, we've found what we are looking for
364 		if (access(path, X_OK) == 0) {
365 			strlcpy(buffer, path, B_PATH_NAME_LENGTH);
366 			return B_OK;
367 		}
368 	}
369 
370 	return B_ENTRY_NOT_FOUND;
371 }
372 
373 
374 static char *
375 next_argument(char **_start, bool separate)
376 {
377 	char *line = *_start;
378 	char quote = 0;
379 	int32 i;
380 
381 	// eliminate leading spaces
382 	while (line[0] == ' ')
383 		line++;
384 
385 	if (line[0] == '"' || line[0] == '\'') {
386 		quote = line[0];
387 		line++;
388 	}
389 
390 	if (!line[0])
391 		return NULL;
392 
393 	for (i = 0;; i++) {
394 		if (line[i] == '\\' && line[i + 1] != '\0')
395 			continue;
396 
397 		if (line[i] == '\0') {
398 			*_start = &line[i];
399 			return line;
400 		}
401 		if ((!quote && line[i] == ' ') || line[i] == quote) {
402 			// argument separator!
403 			if (separate)
404 				line[i] = '\0';
405 			*_start = &line[i + 1];
406 			return line;
407 		}
408 	}
409 
410 	return NULL;
411 }
412 
413 
414 status_t
415 __parse_invoke_line(char *invoker, char ***_newArgs,
416 	char * const **_oldArgs, int32 *_argCount, const char *arg0)
417 {
418 	int32 i, count = 0;
419 	char *arg = invoker;
420 	char **newArgs;
421 
422 	// count arguments in the line
423 
424 	while (next_argument(&arg, false)) {
425 		count++;
426 	}
427 
428 	// this is a shell script and requires special treatment
429 	newArgs = (char**)malloc((*_argCount + count + 1) * sizeof(void *));
430 	if (newArgs == NULL)
431 		return B_NO_MEMORY;
432 
433 	// copy invoker and old arguments and to newArgs
434 
435 	for (i = 0; (arg = next_argument(&invoker, true)) != NULL; i++) {
436 		newArgs[i] = arg;
437 	}
438 	for (i = 0; i < *_argCount; i++) {
439 		if (i == 0)
440 			newArgs[i + count] = (char*)arg0;
441 		else
442 			newArgs[i + count] = (char *)(*_oldArgs)[i];
443 	}
444 
445 	newArgs[i + count] = NULL;
446 
447 	*_newArgs = newArgs;
448 	*_oldArgs = (char * const *)newArgs;
449 	*_argCount += count;
450 
451 	return B_OK;
452 }
453 
454 
455 status_t
456 __get_next_image_dependency(image_id id, uint32 *cookie, const char **_name)
457 {
458 	return __gRuntimeLoader->get_next_image_dependency(id, cookie, _name);
459 }
460 
461 
462 status_t
463 __test_executable(const char *path, char *invoker)
464 {
465 	return __gRuntimeLoader->test_executable(path, invoker);
466 }
467 
468 
469 /*!	Allocates a flat buffer and copies the argument and environment strings
470 	into it. The buffer starts with a char* array which contains pointers to
471 	the strings of the arguments and environment, followed by the strings. Both
472 	arguments and environment arrays are NULL-terminated.
473 
474 	If executablePath is non-NULL, it should refer to the executable to be
475 	executed. If the executable file specifies changes to environment variable
476 	values, those will be performed.
477 */
478 status_t
479 __flatten_process_args(const char* const* args, int32 argCount,
480 	const char* const* env, int32* _envCount, const char* executablePath,
481 	char*** _flatArgs, size_t* _flatSize)
482 {
483 	if (args == NULL || _envCount == NULL || (env == NULL && *_envCount != 0))
484 		return B_BAD_VALUE;
485 
486 	int32 envCount = *_envCount;
487 
488 	// determine total needed size
489 	int32 argSize = 0;
490 	for (int32 i = 0; i < argCount; i++) {
491 		if (args[i] == NULL)
492 			return B_BAD_VALUE;
493 		argSize += strlen(args[i]) + 1;
494 	}
495 
496 	int32 envSize = 0;
497 	for (int32 i = 0; i < envCount; i++) {
498 		if (env[i] == NULL)
499 			return B_BAD_VALUE;
500 		envSize += strlen(env[i]) + 1;
501 	}
502 
503 	EnvironmentFilter envFilter;
504 	if (executablePath != NULL)
505 		envFilter.Init(executablePath, env, envCount);
506 
507 	int32 totalSlotCount = argCount + envCount + 2
508 		+ envFilter.AdditionalSlotsNeeded();
509 	int32 size = totalSlotCount * sizeof(char*) + argSize + envSize
510 		+ envFilter.AdditionalSizeNeeded();
511 	if (size > MAX_PROCESS_ARGS_SIZE)
512 		return B_TOO_MANY_ARGS;
513 
514 	// allocate space
515 	char** flatArgs = (char**)malloc(size);
516 	if (flatArgs == NULL)
517 		return B_NO_MEMORY;
518 
519 	char** slot = flatArgs;
520 	char* stringSpace = (char*)(flatArgs + totalSlotCount);
521 
522 	// copy arguments and environment
523 	for (int32 i = 0; i < argCount; i++) {
524 		int32 argSize = strlen(args[i]) + 1;
525 		memcpy(stringSpace, args[i], argSize);
526 		*slot++ = stringSpace;
527 		stringSpace += argSize;
528 	}
529 
530 	*slot++ = NULL;
531 
532 	for (int32 i = 0; i < envCount; i++) {
533 		size_t envSize = envFilter.PrepareSlot(env[i], i, stringSpace);
534 		*slot++ = stringSpace;
535 		stringSpace += envSize;
536 	}
537 
538 	envFilter.PrepareAdditionalSlots(slot, stringSpace);
539 
540 	*slot++ = NULL;
541 
542 	*_envCount = envCount + envFilter.AdditionalSlotsNeeded();
543 	*_flatArgs = flatArgs;
544 	*_flatSize = stringSpace - (char*)flatArgs;
545 	return B_OK;
546 }
547 
548 
549 extern "C" void _call_init_routines_(void);
550 void
551 _call_init_routines_(void)
552 {
553 	// This is called by the original BeOS startup code.
554 	// We don't need it, because our loader already does the job, right?
555 }
556 
557