96d1007dea91524fc32e6ec41b784f53358db264
[project/ubox.git] / mount_root.c
1 /*
2  * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
3  * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License version 2.1
7  * as published by the Free Software Foundation
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <getopt.h>
18 #include <syslog.h>
19 #include <errno.h>
20 #include <stdlib.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <libgen.h>
24 #include <glob.h>
25 #include <dirent.h>
26
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/mount.h>
30 #include <sys/wait.h>
31
32 #include <asm/byteorder.h>
33
34 #include <mtd/mtd-user.h>
35
36 #define DEBUG(level, fmt, ...) do { \
37         if (debug >= level) \
38                 fprintf(stderr, "%s %s(%d): " fmt, argv0, __func__, __LINE__, ## __VA_ARGS__); \
39         } while (0)
40
41 #define LOG(fmt, ...) do { \
42                 syslog(LOG_INFO, fmt, ## __VA_ARGS__); \
43                 fprintf(stderr, "%s: "fmt, argv0, ## __VA_ARGS__); \
44         } while (0)
45
46 #define ERROR(fmt, ...) do { \
47                 syslog(LOG_ERR, fmt, ## __VA_ARGS__); \
48                 fprintf(stderr, "%s: "fmt, argv0, ## __VA_ARGS__); \
49         } while (0)
50
51 enum {
52         FS_NONE,
53         FS_JFFS2,
54         FS_DEADCODE,
55 };
56
57 static const char *argv0;
58
59 /* this is a raw syscall - man 2 pivot_root */
60 extern int pivot_root(const char *new_root, const char *put_old);
61
62 static int debug = 0;
63
64 static void foreachdir(const char *dir, int (*cb)(const char*))
65 {
66         char globdir[256];
67         glob_t gl;
68         int j;
69
70         if (dir[strlen(dir) - 1] == '/')
71                 snprintf(globdir, 256, "%s*", dir);
72         else
73                 snprintf(globdir, 256, "%s/*", dir);
74
75         if (!glob(globdir, GLOB_NOESCAPE | GLOB_MARK | GLOB_ONLYDIR, NULL, &gl))
76                 for (j = 0; j < gl.gl_pathc; j++)
77                         foreachdir(gl.gl_pathv[j], cb);
78
79         cb(dir);
80 }
81
82 static int find_overlay_mount(char *overlay)
83 {
84         FILE *fp = fopen("/proc/mounts", "r");
85         static char line[256];
86         int ret = -1;
87
88         if(!fp)
89                 return ret;
90
91         while (ret && fgets(line, sizeof(line), fp))
92                 if (!strncmp(line, overlay, strlen(overlay)))
93                         ret = 0;
94
95         fclose(fp);
96
97         return ret;
98 }
99
100 static char* find_mount_point(char *block, char *fs)
101 {
102         FILE *fp = fopen("/proc/mounts", "r");
103         static char line[256];
104         int len = strlen(block);
105         char *point = NULL;
106
107         if(!fp)
108                 return NULL;
109
110         while (fgets(line, sizeof(line), fp)) {
111                 if (!strncmp(line, block, len)) {
112                         char *p = &line[len + 1];
113                         char *t = strstr(p, " ");
114
115                         if (!t)
116                                 return NULL;
117
118                         *t = '\0';
119                         t++;
120
121                         if (fs && strncmp(t, fs, strlen(fs))) {
122                                 ERROR("block is mounted with wrong fs\n");
123                                 return NULL;
124                         }
125                         point = p;
126                         break;
127                 }
128         }
129
130         fclose(fp);
131
132         return point;
133 }
134
135 static char* find_mtd_index(char *name)
136 {
137         FILE *fp = fopen("/proc/mtd", "r");
138         static char line[256];
139         char *index = NULL;
140
141         if(!fp)
142                 return index;
143
144         while (!index && fgets(line, sizeof(line), fp)) {
145                 if (strstr(line, name)) {
146                         char *eol = strstr(line, ":");
147
148                         if (!eol)
149                                 continue;
150
151                         *eol = '\0';
152                         index = &line[3];
153                         DEBUG(1, "found %s -> index:%s\n", name, index);
154                 }
155         }
156
157         fclose(fp);
158
159         return index;
160 }
161
162 static int find_mtd_block(char *name, char *part, int plen)
163 {
164         char *index = find_mtd_index(name);
165
166         if (!index)
167                 return -1;
168
169         snprintf(part, plen, "/dev/mtdblock%s", index);
170         DEBUG(1, "found %s -> %s\n", name, part);
171
172         return 0;
173 }
174
175 static int find_mtd_char(char *name, char *part, int plen)
176 {
177         char *index = find_mtd_index(name);
178
179         if (!index)
180                 return -1;
181
182         snprintf(part, plen, "/dev/mtd%s", index);
183         DEBUG(1, "found %s -> %s\n", name, part);
184
185         return 0;
186 }
187
188 static int mtd_unlock(char *mtd)
189 {
190         struct erase_info_user mtdlock;
191         struct mtd_info_user mtdinfo;
192         int fd = open(mtd, O_RDWR | O_SYNC);
193         int ret = -1;
194
195         DEBUG(1, "%s\n", mtd);
196
197         if (!fd) {
198                 ERROR("failed to open %s: %s\n", mtd, strerror(errno));
199                 return -1;
200         }
201
202         ret = ioctl(fd, MEMGETINFO, &mtdinfo);
203         if (ret) {
204                 ERROR("ioctl(%s, MEMGETINFO) failed: %s\n", mtd, strerror(errno));
205                 goto err_out;
206         }
207
208         mtdlock.start = 0;
209         mtdlock.length = mtdinfo.size;
210         ioctl(fd, MEMUNLOCK, &mtdlock);
211
212 err_out:
213         close(fd);
214
215         return ret;
216 }
217
218 static int mtd_mount_jffs2(void)
219 {
220         char rootfs_data[32];
221
222
223         if (mkdir("/tmp/overlay", 0755)) {
224                 ERROR("failed to mkdir /tmp/overlay: %s\n", strerror(errno));
225                 return -1;
226         }
227
228         if (find_mtd_block("rootfs_data", rootfs_data, sizeof(rootfs_data))) {
229                 ERROR("rootfs_data does not exist\n");
230                 return -1;
231         }
232
233         if (mount(rootfs_data, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) {
234                 ERROR("failed to mount -t jffs2 %s /tmp/overlay: %s\n", rootfs_data, strerror(errno));
235                 return -1;
236         }
237
238         find_mtd_char("rootfs_data", rootfs_data, sizeof(rootfs_data));
239
240         return mtd_unlock(rootfs_data);
241 }
242
243 static int jffs2_ready(char *mtd)
244 {
245         FILE *fp = fopen(mtd, "r");
246         __u32 deadc0de;
247         __u16 jffs2;
248         size_t sz;
249
250         if (!fp) {
251                 ERROR("reading %s failed\n", mtd);
252                 exit(-1);
253         }
254
255         sz = fread(&deadc0de, sizeof(deadc0de), 1, fp);
256         fclose(fp);
257
258         if (sz != 1) {
259                 ERROR("reading %s failed: %s\n", mtd, strerror(errno));
260                 exit(-1);
261         }
262
263         deadc0de = __be32_to_cpu(deadc0de);
264         jffs2 = __be16_to_cpu(deadc0de >> 16);
265
266         if (jffs2 == 0x1985) {
267                 LOG("jffs2 is ready\n");
268                 return FS_JFFS2;
269         }
270
271         if (deadc0de == 0xdeadc0de) {
272                 LOG("jffs2 is not ready - marker found\n");
273                 return FS_DEADCODE;
274         }
275
276         ERROR("No jffs2 marker was found\n");
277
278         return FS_NONE;
279 }
280
281 static int check_fs_exists(char *fs)
282 {
283         FILE *fp = fopen("/proc/filesystems", "r");
284         static char line[256];
285         int ret = -1;
286
287         DEBUG(2, "%s\n", fs);
288
289         if (!fp) {
290                 ERROR("opening /proc/filesystems failed: %s\n", strerror(errno));
291                 goto out;
292         }
293
294         while (ret && fgets(line, sizeof(line), fp))
295                 if (strstr(line, fs))
296                         ret = 0;
297
298         fclose(fp);
299
300 out:
301         return ret;
302 }
303
304 static int mount_move(char *oldroot, char *newroot, char *dir)
305 {
306 #ifndef MS_MOVE
307 #define MS_MOVE (1 << 13)
308 #endif
309         struct stat s;
310         char olddir[64];
311         char newdir[64];
312         int ret;
313
314         DEBUG(2, "%s %s\n", oldroot, dir);
315
316         snprintf(olddir, sizeof(olddir), "%s%s", oldroot, dir);
317         snprintf(newdir, sizeof(newdir), "%s%s", newroot, dir);
318
319         if (stat(olddir, &s) || !S_ISDIR(s.st_mode))
320                 return -1;
321
322         if (stat(newdir, &s) || !S_ISDIR(s.st_mode))
323                 return -1;
324
325         ret = mount(olddir, newdir, NULL, MS_NOATIME | MS_MOVE, NULL);
326
327         if (ret)
328                 DEBUG(1, "failed %s %s: %s\n", olddir, newdir, strerror(errno));
329
330         return ret;
331 }
332
333 static int pivot(char *new, char *old)
334 {
335         char pivotdir[64];
336         int ret;
337
338         DEBUG(2, "%s %s\n", new, old);
339
340         if (mount_move("", new, "/proc"))
341                 return -1;
342
343         snprintf(pivotdir, sizeof(pivotdir), "%s%s", new, old);
344
345         ret = pivot_root(new, pivotdir);
346
347         if (ret < 0) {
348                 ERROR("pivot_root failed %s %s: %s\n", new, pivotdir, strerror(errno));
349                 return -1;
350         }
351
352         mount_move(old, "", "/dev");
353         mount_move(old, "", "/tmp");
354         mount_move(old, "", "/sys");
355         mount_move(old, "", "/overlay");
356         mount_move(old, "", "/extroot");
357
358         return 0;
359 }
360
361
362 static int fopivot(char *rw_root, char *ro_root)
363 {
364         char overlay[64], lowerdir[64];
365
366         DEBUG(2, "%s %s\n", rw_root, ro_root);
367
368         if (check_fs_exists("overlay")) {
369                 ERROR("BUG: no suitable fs found\n");
370                 return -1;
371         }
372
373         snprintf(overlay, sizeof(overlay), "overlayfs:%s", rw_root);
374         snprintf(lowerdir, sizeof(lowerdir), "lowerdir=/,upperdir=%s", rw_root);
375
376         if (mount(overlay, "/mnt", "overlayfs", MS_NOATIME, lowerdir)) {
377                 ERROR("mount failed: %s\n", strerror(errno));
378                 return -1;
379         }
380
381         return pivot("/mnt", ro_root);
382 }
383
384 static int ramoverlay(void)
385 {
386         DEBUG(2, "\n");
387
388         mkdir("/tmp/root", 0755);
389         mount("tmpfs", "/tmp/root", "tmpfs", MS_NOATIME, "mode=0755");
390
391         return fopivot("/tmp/root", "/rom");
392 }
393
394 static int switch2jffs(void)
395 {
396         char mtd[32];
397
398         if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
399                 ERROR("no rootfs_data was found\n");
400                 return -1;
401         }
402
403         if (mount(mtd, "/rom/overlay", "jffs2", MS_NOATIME, NULL)) {
404                 ERROR("failed - mount -t jffs2 %s /rom/overlay: %s\n", mtd, strerror(errno));
405                 return -1;
406         }
407
408         if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) {
409                 ERROR("failed - mount -o remount,ro none: %s\n", strerror(errno));
410                 return -1;
411         }
412
413         system("cp -a /tmp/root/* /rom/overlay");
414
415         if (pivot("/rom", "/mnt")) {
416                 ERROR("failed - pivot /rom /mnt: %s\n", strerror(errno));
417                 return -1;
418         }
419
420         if (mount_move("/mnt", "/tmp/root", "")) {
421                 ERROR("failed - mount -o move /mnt /tmp/root %s\n", strerror(errno));
422                 return -1;
423         }
424
425         return fopivot("/overlay", "/rom");
426 }
427
428 static int handle_whiteout(const char *dir)
429 {
430         struct stat s;
431         char link[256];
432         ssize_t sz;
433         struct dirent **namelist;
434         int n;
435
436         n = scandir(dir, &namelist, NULL, NULL);
437
438         if (n < 1)
439                 return -1;
440
441         while (n--) {
442                 char file[256];
443
444                 snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name);
445                 if (!lstat(file, &s) && S_ISLNK(s.st_mode)) {
446                         sz = readlink(file, link, sizeof(link) - 1);
447                         if (sz > 0) {
448                                 char *orig;
449
450                                 link[sz] = '\0';
451                                 orig = strstr(&file[1], "/");
452                                 if (orig && !strcmp(link, "(overlay-whiteout)")) {
453                                         DEBUG(1, "unlinking %s\n", orig);
454                                         unlink(orig);
455                                 }
456                         }
457                 }
458                 free(namelist[n]);
459         }
460         free(namelist);
461
462         return 0;
463 }
464
465 static int main_switch2jffs(int argc, char **argv)
466 {
467         char mtd[32];
468         char *mp;
469         int ret = -1;
470
471         if (find_overlay_mount("overlayfs:/tmp/root"))
472                 return -1;
473
474         if (check_fs_exists("overlay")) {
475                 ERROR("overlayfs not found\n");
476                 return ret;
477         }
478
479         find_mtd_block("rootfs_data", mtd, sizeof(mtd));
480         mp = find_mount_point(mtd, NULL);
481         if (mp) {
482                 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
483                 return -1;
484         }
485
486         if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
487                 ERROR("no rootfs_data was found\n");
488                 return ret;
489         }
490
491         switch (jffs2_ready(mtd)) {
492         case FS_NONE:
493                 ERROR("no jffs2 marker found\n");
494                 /* fall through */
495
496         case FS_DEADCODE:
497                 ret = switch2jffs();
498                 if (!ret) {
499                         DEBUG(1, "doing fo cleanup\n");
500                         umount2("/tmp/root", MNT_DETACH);
501                         foreachdir("/overlay/", handle_whiteout);
502                 }
503                 break;
504
505         case FS_JFFS2:
506                 ret = mtd_mount_jffs2();
507                 if (ret)
508                         break;
509                 if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
510                         ERROR("switching to jffs2 failed\n");
511                         ret = -1;
512                 }
513                 break;
514         }
515
516         return ret;
517 }
518
519 static int extroot(void)
520 {
521         struct stat s;
522         pid_t pid;
523
524         if (stat("/sbin/block", &s))
525                 return -1;
526
527         pid = fork();
528         if (!pid) {
529                 mkdir("/tmp/extroot", 0755);
530                 execl("/sbin/block", "/sbin/block", "extroot", NULL);
531                 exit(-1);
532         } else if (pid > 0) {
533                 int status;
534
535                 waitpid(pid, &status, 0);
536                 if (!WEXITSTATUS(status)) {
537                         if (mount_move("/tmp", "", "/overlay")) {
538                                 ERROR("moving extroot failed - continue normal boot\n");
539                                 umount("/tmp/overlay");
540                         } else if (fopivot("/overlay", "/rom")) {
541                                 ERROR("switching to extroot failed - continue normal boot\n");
542                                 umount("/overlay");
543                         } else {
544                                 return 0;
545                         }
546                 }
547         }
548         return -1;
549 }
550
551 int main(int argc, char **argv)
552 {
553         char *mp;
554         char mtd[32];
555
556         argv0 = basename(*argv);
557
558         if (!strcmp(basename(*argv), "switch2jffs"))
559                 return main_switch2jffs(argc, argv);
560
561         if (!getenv("PREINIT"))
562                 return -1;
563
564         if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
565                 if (!find_mtd_char("rootfs", mtd, sizeof(mtd)))
566                         mtd_unlock(mtd);
567                 LOG("mounting /dev/root\n");
568                 mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0);
569         } else {
570                 if (!extroot()) {
571                         fprintf(stderr, "mount_root: switched to extroot\n");
572                         return 0;
573                 }
574
575                 switch (jffs2_ready(mtd)) {
576                 case FS_NONE:
577                 case FS_DEADCODE:
578                         return ramoverlay();
579
580                 case FS_JFFS2:
581                         find_mtd_block("rootfs_data", mtd, sizeof(mtd));
582                         mp = find_mount_point(mtd, NULL);
583                         if (mp) {
584                                 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
585                                 return -1;
586                         }
587
588                         mtd_mount_jffs2();
589                         DEBUG(1, "switching to jffs2\n");
590                         if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
591                                 ERROR("switching to jffs2 failed - fallback to ramoverlay\n");
592                                 return ramoverlay();
593                         }
594                 }
595         }
596
597         return 0;
598 }