fix m5sum / uuid of squshfs devices
[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_data", mtd, sizeof(mtd))) {
427                 ERROR("no rootfs_data was found\n");
428                 return -1;
429         }
430
431         if (mount(mtd, "/rom/overlay", "jffs2", MS_NOATIME, NULL)) {
432                 ERROR("failed - mount -t jffs2 %s /rom/overlay: %s\n", mtd, strerror(errno));
433                 return -1;
434         }
435
436         if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) {
437                 ERROR("failed - mount -o remount,ro none: %s\n", strerror(errno));
438                 return -1;
439         }
440
441         system("cp -a /tmp/root/* /rom/overlay");
442
443         if (pivot("/rom", "/mnt")) {
444                 ERROR("failed - pivot /rom /mnt: %s\n", strerror(errno));
445                 return -1;
446         }
447
448         if (mount_move("/mnt", "/tmp/root", "")) {
449                 ERROR("failed - mount -o move /mnt /tmp/root %s\n", strerror(errno));
450                 return -1;
451         }
452
453         return fopivot("/overlay", "/rom");
454 }
455
456 static int handle_whiteout(const char *dir)
457 {
458         struct stat s;
459         char link[256];
460         ssize_t sz;
461         struct dirent **namelist;
462         int n;
463
464         n = scandir(dir, &namelist, NULL, NULL);
465
466         if (n < 1)
467                 return -1;
468
469         while (n--) {
470                 char file[256];
471
472                 snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name);
473                 if (!lstat(file, &s) && S_ISLNK(s.st_mode)) {
474                         sz = readlink(file, link, sizeof(link) - 1);
475                         if (sz > 0) {
476                                 char *orig;
477
478                                 link[sz] = '\0';
479                                 orig = strstr(&file[1], "/");
480                                 if (orig && !strcmp(link, "(overlay-whiteout)")) {
481                                         DEBUG(1, "unlinking %s\n", orig);
482                                         unlink(orig);
483                                 }
484                         }
485                 }
486                 free(namelist[n]);
487         }
488         free(namelist);
489
490         return 0;
491 }
492
493 static int mtd_erase(const char *mtd)
494 {
495         int fd = open(mtd, O_RDWR | O_SYNC);
496         struct mtd_info_user i;
497         struct erase_info_user e;
498         int ret;
499
500         if (!fd) {
501                 ERROR("failed to open %s: %s\n", mtd, strerror(errno));
502                 return -1;
503         }
504
505         ret = ioctl(fd, MEMGETINFO, &i);
506         if (ret) {
507                 ERROR("ioctl(%s, MEMGETINFO) failed: %s\n", mtd, strerror(errno));
508                 return -1;
509         }
510
511         e.length = i.erasesize;
512         for (e.start = 0; e.start < i.size; e.start += i.erasesize) {
513                 ioctl(fd, MEMUNLOCK, &e);
514                 if(ioctl(fd, MEMERASE, &e))
515                         ERROR("Failed to erase block on %s at 0x%x\n", mtd, e.start);
516         }
517
518         close(fd);
519         return 0;
520 }
521
522 static int ask_user(int argc, char **argv)
523 {
524         if ((argc < 2) || strcmp(argv[1], "-y")) {
525                 LOG("This will erase all settings and remove any installed packages. Are you sure? [N/y]\n");
526                 if (getchar() != 'y')
527                         return -1;
528         }
529         return 0;
530
531 }
532
533 static int handle_rmdir(const char *dir)
534 {
535         struct stat s;
536         struct dirent **namelist;
537         int n;
538
539         n = scandir(dir, &namelist, NULL, NULL);
540
541         if (n < 1)
542                 return -1;
543
544         while (n--) {
545                 char file[256];
546
547                 snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name);
548                 if (!lstat(file, &s) && !S_ISDIR(s.st_mode)) {
549                         DEBUG(1, "unlinking %s\n", file);
550                         unlink(file);
551                 }
552                 free(namelist[n]);
553         }
554         free(namelist);
555
556         DEBUG(1, "rmdir %s\n", dir);
557         rmdir(dir);
558
559         return 0;
560 }
561
562 static int main_jffs2reset(int argc, char **argv)
563 {
564         char mtd[32];
565         char *mp;
566
567         if (ask_user(argc, argv))
568                 return -1;
569
570         if (check_fs_exists("overlay")) {
571                 ERROR("overlayfs not found\n");
572                 return -1;
573         }
574
575         if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
576                 ERROR("no rootfs_data was found\n");
577                 return -1;
578         }
579
580         mp = find_mount_point(mtd, "jffs2");
581         if (mp) {
582                 LOG("%s is mounted as %s, only ereasing files\n", mtd, mp);
583                 foreachdir(mp, handle_rmdir);
584                 mount(mp, "/", NULL, MS_REMOUNT, 0);
585         } else {
586                 LOG("%s is not mounted, erasing it\n", mtd);
587                 find_mtd_char("rootfs_data", mtd, sizeof(mtd));
588                 mtd_erase(mtd);
589         }
590
591         return 0;
592 }
593
594 static int main_jffs2mark(int argc, char **argv)
595 {
596         FILE *fp;
597         __u32 deadc0de = __cpu_to_be32(0xdeadc0de);
598         char mtd[32];
599         size_t sz;
600
601         if (ask_user(argc, argv))
602                 return -1;
603
604         if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
605                 ERROR("no rootfs_data was found\n");
606                 return -1;
607         }
608
609         fp = fopen(mtd, "w");
610         LOG("%s - marking with deadc0de\n", mtd);
611         if (!fp) {
612                 ERROR("opening %s failed\n", mtd);
613                 return -1;
614         }
615
616         sz = fwrite(&deadc0de, sizeof(deadc0de), 1, fp);
617         fclose(fp);
618
619         if (sz != 1) {
620                 ERROR("writing %s failed: %s\n", mtd, strerror(errno));
621                 return -1;
622         }
623
624         return 0;
625  }
626 static int main_switch2jffs(int argc, char **argv)
627 {
628         char mtd[32];
629         char *mp;
630         int ret = -1;
631
632         if (find_overlay_mount("overlayfs:/tmp/root"))
633                 return -1;
634
635         if (check_fs_exists("overlay")) {
636                 ERROR("overlayfs not found\n");
637                 return ret;
638         }
639
640         find_mtd_block("rootfs_data", mtd, sizeof(mtd));
641         mp = find_mount_point(mtd, NULL);
642         if (mp) {
643                 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
644                 return -1;
645         }
646
647         if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
648                 ERROR("no rootfs_data was found\n");
649                 return ret;
650         }
651
652         switch (jffs2_ready(mtd)) {
653         case FS_NONE:
654                 ERROR("no jffs2 marker found\n");
655                 /* fall through */
656
657         case FS_DEADCODE:
658                 ret = switch2jffs();
659                 if (!ret) {
660                         DEBUG(1, "doing fo cleanup\n");
661                         umount2("/tmp/root", MNT_DETACH);
662                         foreachdir("/overlay/", handle_whiteout);
663                 }
664                 break;
665
666         case FS_JFFS2:
667                 ret = mtd_mount_jffs2();
668                 if (ret)
669                         break;
670                 if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
671                         ERROR("switching to jffs2 failed\n");
672                         ret = -1;
673                 }
674                 break;
675         }
676
677         return ret;
678 }
679
680 static int extroot(void)
681 {
682         struct stat s;
683         pid_t pid;
684
685         if (stat("/sbin/block", &s))
686                 return -1;
687
688         pid = fork();
689         if (!pid) {
690                 mkdir("/tmp/extroot", 0755);
691                 execl("/sbin/block", "/sbin/block", "extroot", NULL);
692                 exit(-1);
693         } else if (pid > 0) {
694                 int status;
695
696                 waitpid(pid, &status, 0);
697                 if (!WEXITSTATUS(status)) {
698                         if (find_mount("/tmp/mnt")) {
699                                 mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT | MS_RDONLY, 0);
700
701                                 mkdir("/tmp/mnt/proc", 0755);
702                                 mkdir("/tmp/mnt/dev", 0755);
703                                 mkdir("/tmp/mnt/sys", 0755);
704                                 mkdir("/tmp/mnt/tmp", 0755);
705                                 mkdir("/tmp/mnt/rom", 0755);
706
707                                 if (mount_move("/tmp", "", "/mnt")) {
708                                         ERROR("moving pivotroot failed - continue normal boot\n");
709                                         umount("/tmp/mnt");
710                                 } else if (pivot("/mnt", "/rom")) {
711                                         ERROR("switching to pivotroot failed - continue normal boot\n");
712                                         umount("/mnt");
713                                 } else {
714                                         return 0;
715                                 }
716                         } else if (find_mount("/tmp/overlay")) {
717                                 if (mount_move("/tmp", "", "/overlay")) {
718                                         ERROR("moving extroot failed - continue normal boot\n");
719                                         umount("/tmp/overlay");
720                                 } else if (fopivot("/overlay", "/rom")) {
721                                         ERROR("switching to extroot failed - continue normal boot\n");
722                                         umount("/overlay");
723                                 } else {
724                                         return 0;
725                                 }
726                         }
727                 }
728         }
729         return -1;
730 }
731
732 int main(int argc, char **argv)
733 {
734         char *mp;
735         char mtd[32];
736
737         argv0 = basename(*argv);
738
739         if (!strcmp(basename(*argv), "jffs2mark"))
740                 return main_jffs2mark(argc, argv);
741
742         if (!strcmp(basename(*argv), "jffs2reset"))
743                 return main_jffs2reset(argc, argv);
744
745         if (!strcmp(basename(*argv), "switch2jffs"))
746                 return main_switch2jffs(argc, argv);
747
748         if (!getenv("PREINIT"))
749                 return -1;
750
751         if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
752                 if (!find_mtd_char("rootfs", mtd, sizeof(mtd)))
753                         mtd_unlock(mtd);
754                 LOG("mounting /dev/root\n");
755                 mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0);
756         } else {
757                 if (!extroot()) {
758                         fprintf(stderr, "mount_root: switched to extroot\n");
759                         return 0;
760                 }
761
762                 switch (jffs2_ready(mtd)) {
763                 case FS_NONE:
764                 case FS_DEADCODE:
765                         return ramoverlay();
766
767                 case FS_JFFS2:
768                         find_mtd_block("rootfs_data", mtd, sizeof(mtd));
769                         mp = find_mount_point(mtd, NULL);
770                         if (mp) {
771                                 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
772                                 return -1;
773                         }
774
775                         mtd_mount_jffs2();
776                         DEBUG(1, "switching to jffs2\n");
777                         if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
778                                 ERROR("switching to jffs2 failed - fallback to ramoverlay\n");
779                                 return ramoverlay();
780                         }
781                 }
782         }
783
784         return 0;
785 }