Minimal initramfs support (based on Al Viro's work).
[linux-flexiantxendom0-3.2.10.git] / init / initramfs.c
1 #define __KERNEL_SYSCALLS__
2 #include <linux/init.h>
3 #include <linux/fs.h>
4 #include <linux/slab.h>
5 #include <linux/types.h>
6 #include <linux/fcntl.h>
7 #include <linux/unistd.h>
8 #include <linux/delay.h>
9
10 static void __init error(char *x)
11 {
12         panic("populate_root: %s\n", x);
13 }
14
15 static void __init *malloc(int size)
16 {
17         return kmalloc(size, GFP_KERNEL);
18 }
19
20 static void __init free(void *where)
21 {
22         kfree(where);
23 }
24
25 asmlinkage long sys_mkdir(char *name, int mode);
26 asmlinkage long sys_mknod(char *name, int mode, dev_t dev);
27 asmlinkage long sys_symlink(char *old, char *new);
28 asmlinkage long sys_link(char *old, char *new);
29 asmlinkage long sys_write(int fd, void *buf, ssize_t size);
30 asmlinkage long sys_chown(char *name, uid_t uid, gid_t gid);
31 asmlinkage long sys_lchown(char *name, uid_t uid, gid_t gid);
32 asmlinkage long sys_fchown(int fd, uid_t uid, gid_t gid);
33 asmlinkage long sys_chmod(char *name, mode_t mode);
34 asmlinkage long sys_fchmod(int fd, mode_t mode);
35
36 /* link hash */
37
38 static struct hash {
39         int ino, minor, major;
40         struct hash *next;
41         char *name;
42 } *head[32];
43
44 static inline int hash(int major, int minor, int ino)
45 {
46         unsigned long tmp = ino + minor + (major << 3);
47         tmp += tmp >> 5;
48         return tmp & 31;
49 }
50
51 static char __init *find_link(int major, int minor, int ino, char *name)
52 {
53         struct hash **p, *q;
54         for (p = head + hash(major, minor, ino); *p; p = &(*p)->next) {
55                 if ((*p)->ino != ino)
56                         continue;
57                 if ((*p)->minor != minor)
58                         continue;
59                 if ((*p)->major != major)
60                         continue;
61                 return (*p)->name;
62         }
63         q = (struct hash *)malloc(sizeof(struct hash));
64         if (!q)
65                 error("can't allocate link hash entry");
66         q->ino = ino;
67         q->minor = minor;
68         q->major = major;
69         q->name = name;
70         q->next = NULL;
71         *p = q;
72         return NULL;
73 }
74
75 static void __init free_hash(void)
76 {
77         struct hash **p, *q;
78         for (p = head; p < head + 32; p++) {
79                 while (*p) {
80                         q = *p;
81                         *p = q->next;
82                         free(q);
83                 }
84         }
85 }
86
87 /* cpio header parsing */
88
89 static __initdata unsigned long ino, major, minor, nlink;
90 static __initdata mode_t mode;
91 static __initdata unsigned long body_len, name_len;
92 static __initdata uid_t uid;
93 static __initdata gid_t gid;
94 static __initdata dev_t rdev;
95
96 static void __init parse_header(char *s)
97 {
98         unsigned long parsed[12];
99         char buf[9];
100         int i;
101
102         buf[8] = '\0';
103         for (i = 0, s += 6; i < 12; i++, s += 8) {
104                 memcpy(buf, s, 8);
105                 parsed[i] = simple_strtoul(buf, NULL, 16);
106         }
107         ino = parsed[0];
108         mode = parsed[1];
109         uid = parsed[2];
110         gid = parsed[3];
111         nlink = parsed[4];
112         body_len = parsed[6];
113         major = parsed[7];
114         minor = parsed[8];
115         rdev = MKDEV(parsed[9], parsed[10]);
116         name_len = parsed[11];
117 }
118
119 /* FSM */
120
121 enum state {
122         Start,
123         Collect,
124         GotHeader,
125         SkipIt,
126         GotName,
127         CopyFile,
128         GotSymlink,
129         Reset
130 } state, next_state;
131
132 char *victim;
133 unsigned count;
134 loff_t this_header, next_header;
135
136 static inline void eat(unsigned n)
137 {
138         victim += n;
139         this_header += n;
140         count -= n;
141 }
142
143 #define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
144
145 static __initdata char *collected;
146 static __initdata int remains;
147 static __initdata char *collect;
148
149 static void __init read_into(char *buf, unsigned size, enum state next)
150 {
151         if (count >= size) {
152                 collected = victim;
153                 eat(size);
154                 state = next;
155         } else {
156                 collect = collected = buf;
157                 remains = size;
158                 next_state = next;
159                 state = Collect;
160         }
161 }
162
163 static __initdata char *header_buf, *symlink_buf, *name_buf;
164
165 static int __init do_start(void)
166 {
167         read_into(header_buf, 110, GotHeader);
168         return 0;
169 }
170
171 static int __init do_collect(void)
172 {
173         unsigned n = remains;
174         if (count < n)
175                 n = count;
176         memcpy(collect, victim, n);
177         eat(n);
178         collect += n;
179         if (remains -= n)
180                 return 1;
181         state = next_state;
182         return 0;
183 }
184
185 static int __init do_header(void)
186 {
187         parse_header(collected);
188         next_header = this_header + N_ALIGN(name_len) + body_len;
189         next_header = (next_header + 3) & ~3;
190         if (name_len <= 0 || name_len > PATH_MAX)
191                 state = SkipIt;
192         else if (S_ISLNK(mode)) {
193                 if (body_len > PATH_MAX)
194                         state = SkipIt;
195                 else {
196                         collect = collected = symlink_buf;
197                         remains = N_ALIGN(name_len) + body_len;
198                         next_state = GotSymlink;
199                         state = Collect;
200                 }
201         } else if (body_len && !S_ISREG(mode))
202                 state = SkipIt;
203         else
204                 read_into(name_buf, N_ALIGN(name_len), GotName);
205         return 0;
206 }
207
208 static int __init do_skip(void)
209 {
210         if (this_header + count <= next_header) {
211                 eat(count);
212                 return 1;
213         } else {
214                 eat(next_header - this_header);
215                 state = next_state;
216                 return 0;
217         }
218 }
219
220 static int __init do_reset(void)
221 {
222         while(count && *victim == '\0')
223                 eat(1);
224         if (count && (this_header & 3))
225                 error("broken padding");
226         return 1;
227 }
228
229 static int __init maybe_link(void)
230 {
231         if (nlink >= 2) {
232                 char *old = find_link(major, minor, ino, collected);
233                 if (old)
234                         return (sys_link(old, collected) < 0) ? -1 : 1;
235         }
236         return 0;
237 }
238
239 static __initdata int wfd;
240
241 static int __init do_name(void)
242 {
243         state = SkipIt;
244         next_state = Start;
245         if (strcmp(collected, "TRAILER!!!") == 0) {
246                 free_hash();
247                 next_state = Reset;
248                 return 0;
249         }
250         printk(KERN_INFO "-> %s\n", collected);
251         if (S_ISREG(mode)) {
252                 if (maybe_link() >= 0) {
253                         wfd = sys_open(collected, O_WRONLY|O_CREAT, mode);
254                         if (wfd >= 0) {
255                                 sys_fchown(wfd, uid, gid);
256                                 sys_fchmod(wfd, mode);
257                                 state = CopyFile;
258                         }
259                 }
260         } else if (S_ISDIR(mode)) {
261                 sys_mkdir(collected, mode);
262                 sys_chown(collected, uid, gid);
263         } else if (S_ISBLK(mode) || S_ISCHR(mode) ||
264                    S_ISFIFO(mode) || S_ISSOCK(mode)) {
265                 if (maybe_link() == 0) {
266                         sys_mknod(collected, mode, rdev);
267                         sys_chown(collected, uid, gid);
268                 }
269         } else
270                 panic("populate_root: bogus mode: %o\n", mode);
271         return 0;
272 }
273
274 static int __init do_copy(void)
275 {
276         if (count >= body_len) {
277                 sys_write(wfd, victim, body_len);
278                 sys_close(wfd);
279                 eat(body_len);
280                 state = SkipIt;
281                 return 0;
282         } else {
283                 sys_write(wfd, victim, count);
284                 body_len -= count;
285                 eat(count);
286                 return 1;
287         }
288 }
289
290 static int __init do_symlink(void)
291 {
292         collected[N_ALIGN(name_len) + body_len] = '\0';
293         sys_symlink(collected + N_ALIGN(name_len), collected);
294         sys_lchown(collected, uid, gid);
295         state = SkipIt;
296         next_state = Start;
297         return 0;
298 }
299
300 static __initdata int (*actions[])(void) = {
301         [Start]         do_start,
302         [Collect]       do_collect,
303         [GotHeader]     do_header,
304         [SkipIt]        do_skip,
305         [GotName]       do_name,
306         [CopyFile]      do_copy,
307         [GotSymlink]    do_symlink,
308         [Reset]         do_reset,
309 };
310
311 static int __init write_buffer(char *buf, unsigned len)
312 {
313         count = len;
314         victim = buf;
315
316         while (!actions[state]())
317                 ;
318         return len - count;
319 }
320
321 static void __init flush_buffer(char *buf, unsigned len)
322 {
323         int written;
324         while ((written = write_buffer(buf, len)) < len) {
325                 char c = buf[written];
326                 if (c == '0') {
327                         buf += written;
328                         len -= written;
329                         state = Start;
330                         continue;
331                 } else
332                         error("junk in compressed archive");
333         }
334 }
335
336 /*
337  * gzip declarations
338  */
339
340 #define OF(args)  args
341
342 #ifndef memzero
343 #define memzero(s, n)     memset ((s), 0, (n))
344 #endif
345
346 typedef unsigned char  uch;
347 typedef unsigned short ush;
348 typedef unsigned long  ulg;
349
350 #define WSIZE 0x8000    /* window size--must be a power of two, and */
351                         /*  at least 32K for zip's deflate method */
352
353 static uch *inbuf;
354 static uch *window;
355
356 static unsigned insize;  /* valid bytes in inbuf */
357 static unsigned inptr;   /* index of next byte to be processed in inbuf */
358 static unsigned outcnt;  /* bytes in output buffer */
359 static long bytes_out;
360
361 #define get_byte()  (inptr < insize ? inbuf[inptr++] : -1)
362                 
363 /* Diagnostic functions (stubbed out) */
364 #define Assert(cond,msg)
365 #define Trace(x)
366 #define Tracev(x)
367 #define Tracevv(x)
368 #define Tracec(c,x)
369 #define Tracecv(c,x)
370
371 #define STATIC static
372
373 static void flush_window(void);
374 static void error(char *m);
375 static void gzip_mark(void **);
376 static void gzip_release(void **);
377
378 #include "../lib/inflate.c"
379
380 static void __init gzip_mark(void **ptr)
381 {
382 }
383
384 static void __init gzip_release(void **ptr)
385 {
386 }
387
388 /* ===========================================================================
389  * Write the output window window[0..outcnt-1] and update crc and bytes_out.
390  * (Used for the decompressed data only.)
391  */
392 static void __init flush_window(void)
393 {
394         ulg c = crc;         /* temporary variable */
395         unsigned n;
396         uch *in, ch;
397
398         flush_buffer(window, outcnt);
399         in = window;
400         for (n = 0; n < outcnt; n++) {
401                 ch = *in++;
402                 c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8);
403         }
404         crc = c;
405         bytes_out += (ulg)outcnt;
406         outcnt = 0;
407 }
408
409 static void __init unpack_to_rootfs(char *buf, unsigned len)
410 {
411         int written;
412         header_buf = malloc(110);
413         symlink_buf = malloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1);
414         name_buf = malloc(N_ALIGN(PATH_MAX));
415         window = malloc(WSIZE);
416         if (!window || !header_buf || !symlink_buf || !name_buf)
417                 error("can't allocate buffers");
418         state = Start;
419         this_header = 0;
420         while (len) {
421                 loff_t saved_offset = this_header;
422                 if (*buf == '0' && !(this_header & 3)) {
423                         state = Start;
424                         written = write_buffer(buf, len);
425                         buf += written;
426                         len -= written;
427                         continue;
428                 } else if (!*buf) {
429                         buf++;
430                         len--;
431                         this_header++;
432                         continue;
433                 }
434                 this_header = 0;
435                 insize = len;
436                 inbuf = buf;
437                 inptr = 0;
438                 outcnt = 0;             /* bytes in output buffer */
439                 bytes_out = 0;
440                 crc = (ulg)0xffffffffL; /* shift register contents */
441                 makecrc();
442                 if (gunzip())
443                         error("ungzip failed");
444                 if (state != Reset)
445                         error("junk in gzipped archive");
446                 this_header = saved_offset + inptr;
447                 buf += inptr;
448                 len -= inptr;
449         }
450         free(window);
451         free(name_buf);
452         free(symlink_buf);
453         free(header_buf);
454 }
455
456 extern unsigned long __initramfs_start, __initramfs_end;
457
458 void __init populate_rootfs(void)
459 {
460         unpack_to_rootfs((void *) &__initramfs_start,
461                          &__initramfs_end - &__initramfs_start);
462 }