4f97d8809f5055ceee2e4a36e0e9cffdbbde6ae0
[wraplinux.git] / linux.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2008 rPath, Inc. - All Rights Reserved
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
8  *   Boston MA 02110-1301, USA; either version 2 of the License, or
9  *   (at your option) any later version; incorporated herein by reference.
10  *
11  * ----------------------------------------------------------------------- */
12
13 /*
14  * wrap.c
15  *
16  * Actually wrap the kernel image...
17  */
18
19 #include <ctype.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <getopt.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <sysexits.h>
28 #include <sys/types.h>
29 #include "segment.h"
30 #include "setup.h"
31 #include "elf32.h"
32 #include "le.h"
33 #include "wraplinux.h"
34
35 extern char reloc[];
36 extern uint32_t reloc_size;
37
38 /* Find the last instance of a particular command line argument
39    (which should include the final =; do not use for boolean arguments) */
40 static const char *find_argument(const char *cmdline, const char *argument)
41 {
42         int la = strlen(argument);
43         const char *p = cmdline;
44         int wasspace = 1;
45
46         while (*p) {
47                 if (wasspace && !memcmp(p, argument, la))
48                         return p + la;
49
50                 wasspace = isspace(*p++);
51         }
52
53         return NULL;
54 }
55
56 /* Truncate to 32 bits, with saturate */
57 static inline uint32_t saturate32(unsigned long long v)
58 {
59         return (v > 0xffffffff) ? 0xffffffff : (uint32_t) v;
60 }
61
62 /* Get a value with a potential suffix (k/m/g/t/p/e) */
63 static unsigned long long suffix_number(const char *str)
64 {
65         char *ep;
66         unsigned long long v;
67         int shift;
68
69         v = strtoull(str, &ep, 0);
70         switch (*ep | 0x20) {
71         case 'k':
72                 shift = 10;
73                 break;
74         case 'm':
75                 shift = 20;
76                 break;
77         case 'g':
78                 shift = 30;
79                 break;
80         case 't':
81                 shift = 40;
82                 break;
83         case 'p':
84                 shift = 50;
85                 break;
86         case 'e':
87                 shift = 60;
88                 break;
89         default:
90                 shift = 0;
91                 break;
92         }
93         v <<= shift;
94
95         return v;
96 }
97
98 int wrap_kernel(const char *kernel_file, const char *cmdline,
99                 const struct string_list *initrd_list, FILE *out)
100 {
101         struct initrd_info {
102                 int fd;
103                 struct segment seg;
104         } *ird = NULL;
105         int kernel_fd = -1;
106         char *kernel = NULL;
107         size_t kernel_len = 0;
108         struct segment srel, ssup, scmd, skrn;
109         struct startup_info *info = (void *)reloc;
110         struct setup_header *hdr;
111         size_t setup_len;
112         size_t initrd_len;
113         size_t initrd_addr;
114         int rv = EX_SOFTWARE;   /* Should never be returned... */
115         int ninitrd = 0;
116         int i;
117         const struct string_list *ip;
118         int setup_ver;          /* Setup protocol version */
119         int setup_space;        /* How much space for the setup */
120         const char *cmd;
121         uint32_t initrd_max;
122
123         /* Process the kernel file */
124
125         kernel_fd = open(kernel_file, O_RDONLY);
126         if (kernel_fd < 0) {
127                 fprintf(stderr, "%s: %s: %s\n", program, kernel_file,
128                         strerror(errno));
129                 rv = EX_NOINPUT;
130                 goto err;
131         }
132
133         kernel = mapfile(kernel_fd, &kernel_len, 1);
134         if (!kernel) {
135                 fprintf(stderr, "%s: %s: %s\n", program, kernel_file,
136                         strerror(errno));
137                 rv = EX_NOINPUT;
138                 goto err;
139         }
140
141         if (kernel_len < 1024) {
142                 fprintf(stderr, "%s: %s: kernel file too small\n", program,
143                         kernel_file);
144                 rv = EX_NOINPUT;
145                 errno = EINVAL;
146                 goto err;
147         }
148
149         /* Pick apart the header... */
150
151         hdr = (struct setup_header *)(kernel + 0x1f1);
152         setup_len = (hdr->setup_sects + 1) << 9;
153         if (setup_len == 512)
154                 setup_len += 2048;      /* Really old kernel */
155
156         if (rdle32(&hdr->header) != LINUX_MAGIC)
157                 setup_ver = 0;  /* Ancient kernel */
158         else
159                 setup_ver = rdle16(&hdr->version);
160
161         if (setup_ver >= 0x200 && (hdr->loadflags & LOADED_HIGH)) {
162                 skrn.address = 0x100000;
163                 ssup.address = 0x10000;
164                 setup_space = setup_ver >= 0x202 ? 0x10000 : 0xa000;
165         } else {
166                 skrn.address = 0x10000;
167                 ssup.address = 0x90000;
168                 setup_space = 0xa000;
169         }
170
171         if (setup_ver <= 0x200)
172                 initrd_list = NULL;     /* No initrd for ancient kernel */
173
174         if (setup_ver >= 0x203)
175                 initrd_max = rdle32(&hdr->initrd_addr_max);
176         else
177                 initrd_max = 0x37ffffff;
178
179         if ((cmd = find_argument(cmdline, "mem="))) {
180                 uint32_t mem = saturate32(suffix_number(cmd));
181                 if (initrd_max >= mem)
182                         initrd_max = mem - 1;
183         }
184
185         if ((cmd = find_argument(cmdline, "vga="))) {
186                 uint16_t vga;
187
188                 switch (cmd[0] | 0x20) {
189                 case 'a':       /* "ask" */
190                         vga = 0xfffd;
191                         break;
192                 case 'e':       /* "ext" */
193                         vga = 0xfffe;
194                         break;
195                 case 'n':       /* "normal" */
196                         vga = 0xffff;
197                         break;
198                 default:
199                         vga = strtoul(cmd, NULL, 0);
200                         break;
201                 }
202                 wrle16(vga, &hdr->vid_mode);
203         }
204
205         /* Process the initrd file(s) */
206
207         ninitrd = 0;
208         for (ip = initrd_list; ip; ip = ip->next)
209                 ninitrd++;
210
211         if (ninitrd) {
212                 ird = xcalloc(ninitrd, sizeof *ird);
213                 for (i = 0; i < ninitrd; i++)
214                         ird[i].fd = -1;
215         }
216
217         initrd_len = 0;
218         for (ip = initrd_list, i = 0; ip; ip = ip->next, i++) {
219                 /* Each sub-initrd is aligned to a 4-byte boundary */
220                 initrd_len = align_up(initrd_len, 2);
221
222                 ird[i].fd = open(ip->str, O_RDONLY);
223                 if (ird[i].fd < 0) {
224                         fprintf(stderr, "%s: %s: %s", program, ip->str,
225                                 strerror(errno));
226                         rv = EX_NOINPUT;
227                         goto err;
228                 }
229
230                 ird[i].seg.data = mapfile(ird[i].fd, &ird[i].seg.length, 0);
231                 if (!ird[i].seg.data) {
232                         fprintf(stderr, "%s: %s: %s", program, ip->str,
233                                 strerror(errno));
234                         rv = EX_NOINPUT;
235                         goto err;
236                 }
237
238                 initrd_len += ird[i].seg.length;
239         }
240
241         /* Segment: relocation code */
242         /* We put this immediately after the kernel setup, in the memory
243            which will be reclaimed for setup. */
244         srel.next = &ssup;
245         srel.address = (ssup.address + setup_len + 15) & ~15;
246         srel.align = 4;         /* 2**4 = 16 bytes */
247         srel.length = reloc_size;
248         srel.sh_type = SHT_PROGBITS;
249         srel.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR;
250         srel.name = "reloc";
251         srel.data = reloc;
252
253         /* Segment: Linux kernel setup */
254         ssup.next = &scmd;
255         ssup.align = 4;         /* 2**4 = 16 bytes */
256         ssup.length = setup_len;
257         ssup.sh_type = SHT_PROGBITS;
258         ssup.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR;
259         ssup.name = "setup";
260         ssup.data = kernel;
261         if (setup_ver >= 0x200)
262                 hdr->type_of_loader = 0xff;     /* "Other modern loader" */
263
264         /* Segment: kernel command line */
265         scmd.next = &skrn;
266         scmd.length = strlen(cmdline) + 1;
267         scmd.address = (ssup.address + setup_space - scmd.length) & ~15;
268         scmd.align = 4;         /* 2**4 = 16 bytes */
269         if (srel.address + reloc_size > scmd.address) {
270                 /* Uh-oh, we're short on space... push the command line
271                    higher. */
272                 scmd.address = (srel.address + reloc_size + 15) & ~15;
273         }
274         scmd.sh_type = SHT_PROGBITS;
275         scmd.sh_flags = SHF_ALLOC;
276         scmd.name = "cmdline";
277         scmd.data = cmdline;
278         if (setup_ver >= 0x202) {
279                 wrle32(scmd.address, &hdr->cmd_line_ptr);
280         } else {
281                 /* Old-style command line protocol */
282                 wrle16(OLD_CMDLINE_MAGIC, (uint16_t *) (kernel + 0x20));
283                 wrle16(scmd.address - ssup.address,
284                        (uint16_t *) (kernel + 0x22));
285         }
286         if (setup_ver >= 0x201) {
287                 wrle16(scmd.address - ssup.address - 0x200, &hdr->heap_end_ptr);
288                 hdr->loadflags |= CAN_USE_HEAP;
289         }
290
291         /* Segment: Linux kernel proper */
292         skrn.next = ninitrd ? &ird[0].seg : NULL;
293         skrn.align = 4;         /* 2**4 = 16 bytes */
294         skrn.length = kernel_len - setup_len;
295         skrn.sh_type = SHT_PROGBITS;
296         skrn.sh_flags = SHF_ALLOC;
297         skrn.name = "kernel";
298         skrn.data = kernel + setup_len;
299
300         /* Additional segments: initrd */
301         if (skrn.address < 0x100000)
302                 initrd_addr = 0x100000;
303         else
304                 initrd_addr = skrn.address + skrn.length;
305
306         for (i = 0; i < ninitrd; i++) {
307                 char *name;
308
309                 initrd_addr = align_up(initrd_addr, 2);
310
311                 ird[i].seg.next = (i < ninitrd - 1) ? &ird[i + 1].seg : 0;
312                 ird[i].seg.address = initrd_addr;
313                 ird[i].seg.align = 2;   /* 2**2 = 4 bytes */
314                 ird[i].seg.sh_type = SHT_PROGBITS;
315                 ird[i].seg.sh_flags = SHF_ALLOC;
316                 xasprintf(&name, "initrd.%d", i);
317                 ird[i].seg.name = name;
318
319                 initrd_addr += ird[i].seg.length;
320         }
321         if (setup_ver >= 0x200)
322                 wrle32(initrd_len, &hdr->ramdisk_size);
323
324         /* Set up the startup info */
325         wrle32(ninitrd ? ird[0].seg.address : 0, &info->rd_addr);
326         wrle32(initrd_len, &info->rd_len);
327         wrle32(initrd_max, &info->rd_maxaddr);
328         wrle32(ssup.address, &info->setup_addr);
329         wrle32(scmd.address, &info->cmdline_addr);
330
331         rv = opt.output(&srel, srel.address + sizeof *info, out);
332
333  err:
334         if (ird) {
335                 for (i = 0; i < ninitrd; i++)
336                         unmapfile(ird[i].fd, (void *)ird[i].seg.data,
337                                   ird[i].seg.length);
338                 free(ird);
339         }
340
341         unmapfile(kernel_fd, kernel, kernel_len);
342         return rv;
343 }