add support for patched rootfs
[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(char *mp)
101 {
102 FILE *fp = fopen("/proc/mounts", "r");
103 static char line[256];
104 char *point = NULL;
105
106 if(!fp)
107 return NULL;
108
109 while (fgets(line, sizeof(line), fp)) {
110 char *s, *t = strstr(line, " ");
111
112 if (!t)
113 return NULL;
114 t++;
115 s = strstr(t, " ");
116 if (!s)
117 return NULL;
118 *s = '\0';
119
120 if (!strcmp(t, mp)) {
121 fclose(fp);
122 return t;
123 }
124 }
125
126 fclose(fp);
127
128 return point;
129 }
130
131 static char* find_mount_point(char *block, char *fs)
132 {
133 FILE *fp = fopen("/proc/mounts", "r");
134 static char line[256];
135 int len = strlen(block);
136 char *point = NULL;
137
138 if(!fp)
139 return NULL;
140
141 while (fgets(line, sizeof(line), fp)) {
142 if (!strncmp(line, block, len)) {
143 char *p = &line[len + 1];
144 char *t = strstr(p, " ");
145
146 if (!t)
147 return NULL;
148
149 *t = '\0';
150 t++;
151
152 if (fs && strncmp(t, fs, strlen(fs))) {
153 ERROR("block is mounted with wrong fs\n");
154 return NULL;
155 }
156 point = p;
157 break;
158 }
159 }
160
161 fclose(fp);
162
163 return point;
164 }
165
166 static char* find_mtd_index(char *name)
167 {
168 FILE *fp = fopen("/proc/mtd", "r");
169 static char line[256];
170 char *index = NULL;
171
172 if(!fp)
173 return index;
174
175 while (!index && fgets(line, sizeof(line), fp)) {
176 if (strstr(line, name)) {
177 char *eol = strstr(line, ":");
178
179 if (!eol)
180 continue;
181
182 *eol = '\0';
183 index = &line[3];
184 DEBUG(1, "found %s -> index:%s\n", name, index);
185 }
186 }
187
188 fclose(fp);
189
190 return index;
191 }
192
193 static int find_mtd_block(char *name, char *part, int plen)
194 {
195 char *index = find_mtd_index(name);
196
197 if (!index)
198 return -1;
199
200 snprintf(part, plen, "/dev/mtdblock%s", index);
201 DEBUG(1, "found %s -> %s\n", name, part);
202
203 return 0;
204 }
205
206 static int find_mtd_char(char *name, char *part, int plen)
207 {
208 char *index = find_mtd_index(name);
209
210 if (!index)
211 return -1;
212
213 snprintf(part, plen, "/dev/mtd%s", index);
214 DEBUG(1, "found %s -> %s\n", name, part);
215
216 return 0;
217 }
218
219 static int mtd_unlock(char *mtd)
220 {
221 struct erase_info_user mtdlock;
222 struct mtd_info_user mtdinfo;
223 int fd = open(mtd, O_RDWR | O_SYNC);
224 int ret = -1;
225
226 DEBUG(1, "%s\n", mtd);
227
228 if (!fd) {
229 ERROR("failed to open %s: %s\n", mtd, strerror(errno));
230 return -1;
231 }
232
233 ret = ioctl(fd, MEMGETINFO, &mtdinfo);
234 if (ret) {
235 ERROR("ioctl(%s, MEMGETINFO) failed: %s\n", mtd, strerror(errno));
236 goto err_out;
237 }
238
239 mtdlock.start = 0;
240 mtdlock.length = mtdinfo.size;
241 ioctl(fd, MEMUNLOCK, &mtdlock);
242
243 err_out:
244 close(fd);
245
246 return ret;
247 }
248
249 static int mtd_mount_jffs2(void)
250 {
251 char rootfs_data[32];
252
253 if (mkdir("/tmp/overlay", 0755)) {
254 ERROR("failed to mkdir /tmp/overlay: %s\n", strerror(errno));
255 return -1;
256 }
257
258 if (find_mtd_block("rootfs_data", rootfs_data, sizeof(rootfs_data))) {
259 ERROR("rootfs_data does not exist\n");
260 return -1;
261 }
262
263 if (mount(rootfs_data, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) {
264 ERROR("failed to mount -t jffs2 %s /tmp/overlay: %s\n", rootfs_data, strerror(errno));
265 return -1;
266 }
267
268 find_mtd_char("rootfs_data", rootfs_data, sizeof(rootfs_data));
269
270 return mtd_unlock(rootfs_data);
271 }
272
273 static int jffs2_ready(char *mtd)
274 {
275 FILE *fp = fopen(mtd, "r");
276 __u32 deadc0de;
277 __u16 jffs2;
278 size_t sz;
279
280 if (!fp) {
281 ERROR("reading %s failed\n", mtd);
282 exit(-1);
283 }
284
285 sz = fread(&deadc0de, sizeof(deadc0de), 1, fp);
286 fclose(fp);
287
288 if (sz != 1) {
289 ERROR("reading %s failed: %s\n", mtd, strerror(errno));
290 exit(-1);
291 }
292
293 deadc0de = __be32_to_cpu(deadc0de);
294 jffs2 = __be16_to_cpu(deadc0de >> 16);
295
296 if (jffs2 == 0x1985) {
297 LOG("jffs2 is ready\n");
298 return FS_JFFS2;
299 }
300
301 if (deadc0de == 0xdeadc0de) {
302 LOG("jffs2 is not ready - marker found\n");
303 return FS_DEADCODE;
304 }
305
306 ERROR("No jffs2 marker was found\n");
307
308 return FS_NONE;
309 }
310
311 static int check_fs_exists(char *fs)
312 {
313 FILE *fp = fopen("/proc/filesystems", "r");
314 static char line[256];
315 int ret = -1;
316
317 DEBUG(2, "%s\n", fs);
318
319 if (!fp) {
320 ERROR("opening /proc/filesystems failed: %s\n", strerror(errno));
321 goto out;
322 }
323
324 while (ret && fgets(line, sizeof(line), fp))
325 if (strstr(line, fs))
326 ret = 0;
327
328 fclose(fp);
329
330 out:
331 return ret;
332 }
333
334 static int mount_move(char *oldroot, char *newroot, char *dir)
335 {
336 #ifndef MS_MOVE
337 #define MS_MOVE (1 << 13)
338 #endif
339 struct stat s;
340 char olddir[64];
341 char newdir[64];
342 int ret;
343
344 DEBUG(2, "%s %s %s\n", oldroot, newroot, dir);
345
346 snprintf(olddir, sizeof(olddir), "%s%s", oldroot, dir);
347 snprintf(newdir, sizeof(newdir), "%s%s", newroot, dir);
348
349 if (stat(olddir, &s) || !S_ISDIR(s.st_mode))
350 return -1;
351
352 if (stat(newdir, &s) || !S_ISDIR(s.st_mode))
353 return -1;
354
355 ret = mount(olddir, newdir, NULL, MS_NOATIME | MS_MOVE, NULL);
356
357 if (ret)
358 DEBUG(1, "failed %s %s: %s\n", olddir, newdir, strerror(errno));
359
360 return ret;
361 }
362
363 static int pivot(char *new, char *old)
364 {
365 char pivotdir[64];
366 int ret;
367
368 DEBUG(2, "%s %s\n", new, old);
369
370 if (mount_move("", new, "/proc"))
371 return -1;
372
373 snprintf(pivotdir, sizeof(pivotdir), "%s%s", new, old);
374
375 ret = pivot_root(new, pivotdir);
376
377 if (ret < 0) {
378 ERROR("pivot_root failed %s %s: %s\n", new, pivotdir, strerror(errno));
379 return -1;
380 }
381
382 mount_move(old, "", "/dev");
383 mount_move(old, "", "/tmp");
384 mount_move(old, "", "/sys");
385 mount_move(old, "", "/overlay");
386
387 return 0;
388 }
389
390 static int fopivot(char *rw_root, char *ro_root)
391 {
392 char overlay[64], lowerdir[64];
393
394 DEBUG(2, "%s %s\n", rw_root, ro_root);
395
396 if (check_fs_exists("overlay")) {
397 ERROR("BUG: no suitable fs found\n");
398 return -1;
399 }
400
401 snprintf(overlay, sizeof(overlay), "overlayfs:%s", rw_root);
402 snprintf(lowerdir, sizeof(lowerdir), "lowerdir=/,upperdir=%s", rw_root);
403
404 if (mount(overlay, "/mnt", "overlayfs", MS_NOATIME, lowerdir)) {
405 ERROR("mount failed: %s\n", strerror(errno));
406 return -1;
407 }
408
409 return pivot("/mnt", ro_root);
410 }
411
412 static int ramoverlay(void)
413 {
414 DEBUG(2, "\n");
415
416 mkdir("/tmp/root", 0755);
417 mount("tmpfs", "/tmp/root", "tmpfs", MS_NOATIME, "mode=0755");
418
419 return fopivot("/tmp/root", "/rom");
420 }
421
422 static int switch2jffs(void)
423 {
424 char mtd[32];
425
426 if (!find_mtd_block("rootfs_patches", mtd, sizeof(mtd)))
427 return 0;
428
429 if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
430 ERROR("no rootfs_data was found\n");
431 return -1;
432 }
433
434 if (mount(mtd, "/rom/overlay", "jffs2", MS_NOATIME, NULL)) {
435 ERROR("failed - mount -t jffs2 %s /rom/overlay: %s\n", mtd, strerror(errno));
436 return -1;
437 }
438
439 if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) {
440 ERROR("failed - mount -o remount,ro none: %s\n", strerror(errno));
441 return -1;
442 }
443
444 system("cp -a /tmp/root/* /rom/overlay");
445
446 if (pivot("/rom", "/mnt")) {
447 ERROR("failed - pivot /rom /mnt: %s\n", strerror(errno));
448 return -1;
449 }
450
451 if (mount_move("/mnt", "/tmp/root", "")) {
452 ERROR("failed - mount -o move /mnt /tmp/root %s\n", strerror(errno));
453 return -1;
454 }
455
456 return fopivot("/overlay", "/rom");
457 }
458
459 static int handle_whiteout(const char *dir)
460 {
461 struct stat s;
462 char link[256];
463 ssize_t sz;
464 struct dirent **namelist;
465 int n;
466
467 n = scandir(dir, &namelist, NULL, NULL);
468
469 if (n < 1)
470 return -1;
471
472 while (n--) {
473 char file[256];
474
475 snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name);
476 if (!lstat(file, &s) && S_ISLNK(s.st_mode)) {
477 sz = readlink(file, link, sizeof(link) - 1);
478 if (sz > 0) {
479 char *orig;
480
481 link[sz] = '\0';
482 orig = strstr(&file[1], "/");
483 if (orig && !strcmp(link, "(overlay-whiteout)")) {
484 DEBUG(1, "unlinking %s\n", orig);
485 unlink(orig);
486 }
487 }
488 }
489 free(namelist[n]);
490 }
491 free(namelist);
492
493 return 0;
494 }
495
496 static int mtd_erase(const char *mtd)
497 {
498 int fd = open(mtd, O_RDWR | O_SYNC);
499 struct mtd_info_user i;
500 struct erase_info_user e;
501 int ret;
502
503 if (!fd) {
504 ERROR("failed to open %s: %s\n", mtd, strerror(errno));
505 return -1;
506 }
507
508 ret = ioctl(fd, MEMGETINFO, &i);
509 if (ret) {
510 ERROR("ioctl(%s, MEMGETINFO) failed: %s\n", mtd, strerror(errno));
511 return -1;
512 }
513
514 e.length = i.erasesize;
515 for (e.start = 0; e.start < i.size; e.start += i.erasesize) {
516 ioctl(fd, MEMUNLOCK, &e);
517 if(ioctl(fd, MEMERASE, &e))
518 ERROR("Failed to erase block on %s at 0x%x\n", mtd, e.start);
519 }
520
521 close(fd);
522 return 0;
523 }
524
525 static int ask_user(int argc, char **argv)
526 {
527 if ((argc < 2) || strcmp(argv[1], "-y")) {
528 LOG("This will erase all settings and remove any installed packages. Are you sure? [N/y]\n");
529 if (getchar() != 'y')
530 return -1;
531 }
532 return 0;
533
534 }
535
536 static int handle_rmdir(const char *dir)
537 {
538 struct stat s;
539 struct dirent **namelist;
540 int n;
541
542 n = scandir(dir, &namelist, NULL, NULL);
543
544 if (n < 1)
545 return -1;
546
547 while (n--) {
548 char file[256];
549
550 snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name);
551 if (!lstat(file, &s) && !S_ISDIR(s.st_mode)) {
552 DEBUG(1, "unlinking %s\n", file);
553 unlink(file);
554 }
555 free(namelist[n]);
556 }
557 free(namelist);
558
559 DEBUG(1, "rmdir %s\n", dir);
560 rmdir(dir);
561
562 return 0;
563 }
564
565 static int main_jffs2reset(int argc, char **argv)
566 {
567 char mtd[32];
568 char *mp;
569
570 if (ask_user(argc, argv))
571 return -1;
572
573 if (check_fs_exists("overlay")) {
574 ERROR("overlayfs not found\n");
575 return -1;
576 }
577
578 if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
579 ERROR("no rootfs_data was found\n");
580 return -1;
581 }
582
583 mp = find_mount_point(mtd, "jffs2");
584 if (mp) {
585 LOG("%s is mounted as %s, only ereasing files\n", mtd, mp);
586 foreachdir(mp, handle_rmdir);
587 mount(mp, "/", NULL, MS_REMOUNT, 0);
588 } else {
589 LOG("%s is not mounted, erasing it\n", mtd);
590 find_mtd_char("rootfs_data", mtd, sizeof(mtd));
591 mtd_erase(mtd);
592 }
593
594 return 0;
595 }
596
597 static int main_jffs2mark(int argc, char **argv)
598 {
599 FILE *fp;
600 __u32 deadc0de = __cpu_to_be32(0xdeadc0de);
601 char mtd[32];
602 size_t sz;
603
604 if (ask_user(argc, argv))
605 return -1;
606
607 if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
608 ERROR("no rootfs_data was found\n");
609 return -1;
610 }
611
612 fp = fopen(mtd, "w");
613 LOG("%s - marking with deadc0de\n", mtd);
614 if (!fp) {
615 ERROR("opening %s failed\n", mtd);
616 return -1;
617 }
618
619 sz = fwrite(&deadc0de, sizeof(deadc0de), 1, fp);
620 fclose(fp);
621
622 if (sz != 1) {
623 ERROR("writing %s failed: %s\n", mtd, strerror(errno));
624 return -1;
625 }
626
627 return 0;
628 }
629 static int main_switch2jffs(int argc, char **argv)
630 {
631 char mtd[32];
632 char *mp;
633 int ret = -1;
634
635 if (find_overlay_mount("overlayfs:/tmp/root"))
636 return -1;
637
638 if (check_fs_exists("overlay")) {
639 ERROR("overlayfs not found\n");
640 return ret;
641 }
642
643 find_mtd_block("rootfs_data", mtd, sizeof(mtd));
644 mp = find_mount_point(mtd, NULL);
645 if (mp) {
646 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
647 return -1;
648 }
649
650 if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
651 ERROR("no rootfs_data was found\n");
652 return ret;
653 }
654
655 switch (jffs2_ready(mtd)) {
656 case FS_NONE:
657 ERROR("no jffs2 marker found\n");
658 /* fall through */
659
660 case FS_DEADCODE:
661 ret = switch2jffs();
662 if (!ret) {
663 DEBUG(1, "doing fo cleanup\n");
664 umount2("/tmp/root", MNT_DETACH);
665 foreachdir("/overlay/", handle_whiteout);
666 }
667 break;
668
669 case FS_JFFS2:
670 ret = mtd_mount_jffs2();
671 if (ret)
672 break;
673 if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
674 ERROR("switching to jffs2 failed\n");
675 ret = -1;
676 }
677 break;
678 }
679
680 return ret;
681 }
682
683 static int extroot(const char *prefix)
684 {
685 char block_path[32];
686 char kmod_loader[64];
687 struct stat s;
688 pid_t pid;
689
690 sprintf(block_path, "%s/sbin/block", prefix);
691
692 if (stat(block_path, &s))
693 return -1;
694
695 sprintf(kmod_loader, "/sbin/kmodloader %s/etc/modules-boot.d/ %s", prefix, prefix);
696 system(kmod_loader);
697
698 pid = fork();
699 if (!pid) {
700 mkdir("/tmp/extroot", 0755);
701 execl(block_path, block_path, "extroot", NULL);
702 exit(-1);
703 } else if (pid > 0) {
704 int status;
705
706 waitpid(pid, &status, 0);
707 if (!WEXITSTATUS(status)) {
708 if (find_mount("/tmp/extroot/mnt")) {
709 mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT | MS_RDONLY, 0);
710
711 mkdir("/tmp/extroot/mnt/proc", 0755);
712 mkdir("/tmp/extroot/mnt/dev", 0755);
713 mkdir("/tmp/extroot/mnt/sys", 0755);
714 mkdir("/tmp/extroot/mnt/tmp", 0755);
715 mkdir("/tmp/extroot/mnt/rom", 0755);
716
717 if (mount_move("/tmp/extroot", "", "/mnt")) {
718 ERROR("moving pivotroot failed - continue normal boot\n");
719 umount("/tmp/extroot/mnt");
720 } else if (pivot("/mnt", "/rom")) {
721 ERROR("switching to pivotroot failed - continue normal boot\n");
722 umount("/mnt");
723 } else {
724 umount("/tmp/overlay");
725 rmdir("/tmp/overlay");
726 rmdir("/tmp/extroot/mnt");
727 rmdir("/tmp/extroot");
728 return 0;
729 }
730 } else if (find_mount("/tmp/extroot/overlay")) {
731 if (mount_move("/tmp/extroot", "", "/overlay")) {
732 ERROR("moving extroot failed - continue normal boot\n");
733 umount("/tmp/extroot/overlay");
734 } else if (fopivot("/overlay", "/rom")) {
735 ERROR("switching to extroot failed - continue normal boot\n");
736 umount("/overlay");
737 } else {
738 umount("/tmp/overlay");
739 rmdir("/tmp/overlay");
740 rmdir("/tmp/extroot/overlay");
741 rmdir("/tmp/extroot");
742 return 0;
743 }
744 }
745 }
746 }
747 return -1;
748 }
749
750 int main(int argc, char **argv)
751 {
752 char *mp;
753 char mtd[32];
754
755 argv0 = basename(*argv);
756
757 if (!strcmp(basename(*argv), "jffs2mark"))
758 return main_jffs2mark(argc, argv);
759
760 if (!strcmp(basename(*argv), "jffs2reset"))
761 return main_jffs2reset(argc, argv);
762
763 if (!strcmp(basename(*argv), "switch2jffs"))
764 return main_switch2jffs(argc, argv);
765
766 if (!getenv("PREINIT"))
767 return -1;
768
769 if (!find_mtd_block("rootfs_patches", mtd, sizeof(mtd))) {
770 ramoverlay();
771 } else if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
772 if (!find_mtd_char("rootfs", mtd, sizeof(mtd)))
773 mtd_unlock(mtd);
774 LOG("mounting /dev/root\n");
775 mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0);
776 } else {
777 if (!extroot("")) {
778 fprintf(stderr, "mount_root: switched to extroot\n");
779 return 0;
780 }
781
782 switch (jffs2_ready(mtd)) {
783 case FS_NONE:
784 case FS_DEADCODE:
785 return ramoverlay();
786
787 case FS_JFFS2:
788 find_mtd_block("rootfs_data", mtd, sizeof(mtd));
789 mp = find_mount_point(mtd, NULL);
790 if (mp) {
791 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
792 return -1;
793 }
794
795 mtd_mount_jffs2();
796
797 if (!extroot("/tmp/overlay")) {
798 fprintf(stderr, "mount_root: switched to extroot\n");
799 return 0;
800 }
801
802 DEBUG(1, "switching to jffs2\n");
803 if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
804 ERROR("switching to jffs2 failed - fallback to ramoverlay\n");
805 return ramoverlay();
806 }
807 }
808 }
809
810 return 0;
811 }