aboutsummaryrefslogtreecommitdiff
path: root/lib/initrd.c
blob: 62a4e209aac476dc63b8f2145c85ddca8bde5db2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <fbos/init.h>
#include <fbos/sched.h>
#include <fbos/printk.h>
#include <fbos/string.h>

#define BUFFER_SIZE 32

#define CPIO_HEADER_FILESIZE 54
#define CPIO_HEADER_NAMESIZE 94
#define CPIO_HEADER_SIZE 110

// Converts the given string 'str' of 'n' length to an integer by assuming it's
// in base 16.
__kernel uint64_t strtoul16(const char *str, size_t n)
{
	char c;
	uint64_t ret = 0;
	uint64_t aux = 0;

	for (uint64_t i = 1; n > 0; i *= 16, n--) {
		c = str[n - 1];
		if (c >= 'A' && c <= 'F') {
			aux = 10 + (uint64_t)(c - 'A');
		} else if (c >= 'a' && c <= 'f') {
			aux = 10 + (uint64_t)(c - 'a');
		} else if (c < '0' || c > '9') {
			die("Bad number\n");
		} else {
			aux = (uint64_t)c - '0';
		}

		ret += aux * i;
	}
	return ret;
}

// Returns the 'enum taks_id' that can be gathered by the given path.
__kernel int get_task_id_from_path(const char *const path)
{
	if (strcmp(path, "usr/bin/init") == 0) {
		return TASK_INIT;
	} else if (strcmp(path, "usr/bin/fizz") == 0) {
		return TASK_FIZZ;
	} else if (strcmp(path, "usr/bin/buzz") == 0) {
		return TASK_BUZZ;
	} else if (strcmp(path, "usr/bin/fizzbuzz") == 0) {
		return TASK_FIZZBUZZ;
	}
	return TASK_UNKNOWN;
}

// Fetch the 'entry_addr' for the ELF binary pointed by 'addr'. If everything
// goes right, the task identified by 'task_id' will finally be initialized with
// said address.
__kernel const void *get_task_entry_addr(const unsigned char *const addr)
{
	uint64_t offset;

	if (addr[0] != 0x7F || memcmp(&addr[1], "ELF", 3) != 0) {
		die("Bad ELF format\n");
	}
	if (addr[4] != 2) {
		die("64-bit format is mandatory\n");
	}
	if (addr[5] != 1) {
		die("Little-endian only\n");
	}

	offset = (uint64_t)addr[0x18];
	return (const void *)(addr + offset);
}

__kernel void extract_initrd(const unsigned char *const initrd_addr, uint64_t size,
							 struct task_struct g_tasks[4])
{
	char buffer[BUFFER_SIZE];
	uint64_t name_size, file_size, padding, base = 0;
	int task_id;

	// The `base` is the index from `initrd_addr` which points to the first byte
	// of the header of the currently evaluated file inside of the CPIO archive.
	while (base < size) {
		// Only the "newc" format is supported, without checksums nor fancy
		// stuff.
		if (memcmp(&initrd_addr[base], "070701", 6) != 0) {
			if (memcmp(&initrd_addr[base], "070702", 6) == 0 ||
				memcmp(&initrd_addr[base], "070707", 6) == 0) {
				die("Incorrect cpio format: stick to 'newc'");
			} else {
				die("No cpio magic number");
			}
		}

		// We identify the task being extracted by looking at the file's path,
		// so let's first get the size of it.
		memcpy(buffer, &initrd_addr[base + CPIO_HEADER_NAMESIZE], 8);
		buffer[8] = '\0';
		name_size = strtoul16(buffer, 8);
		if (name_size >= BUFFER_SIZE) {
			die("Path too large for initrd executable");
		}

		// Right after the header (hence current header + its size) there is the
		// actual file's path, which is exactly `name_size` long. Fetch it now
		// to identify the task at hand.
		memcpy(buffer, &initrd_addr[base + CPIO_HEADER_SIZE], name_size);
		buffer[name_size] = '\0';
		task_id = get_task_id_from_path(buffer);

		// Note that this is not necessarily a bad CPIO archive, it might just
		// be the end "TRAILER!!!" delimiter. Either way, just quit at this
		// point.
		if (task_id == TASK_UNKNOWN) {
			break;
		}

		// Fetch the size of the executable, which is needed for `extract_elf`,
		// as well as for advancing the `base` to the next file.
		memcpy(buffer, &initrd_addr[base + CPIO_HEADER_FILESIZE], 8);
		buffer[8] = '\0';
		file_size = strtoul16(buffer, 8);

		// Files are aligned in 4-byte boundaries after the header. That's why
		// there might be some padding in between the header and the file.
		padding = 4 - ((CPIO_HEADER_SIZE + name_size) & 3);

		// And extract the entry address for the current task from the ELF
		// binary.
		g_tasks[task_id].entry_addr =
			get_task_entry_addr(&initrd_addr[base + name_size + CPIO_HEADER_SIZE + padding]);

		// Advance the base to the next file.
		base += CPIO_HEADER_SIZE + name_size + padding + file_size;
	}
}