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