add support for module handling
[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
31 #include <asm/byteorder.h>
32
33 #include <mtd/mtd-user.h>
34
35 #define DEBUG(level, fmt, ...) do { \
36         if (debug >= level) \
37                 fprintf(stderr, "%s %s(%d): " fmt, argv0, __func__, __LINE__, ## __VA_ARGS__); \
38         } while (0)
39
40 #define LOG(fmt, ...) do { \
41                 syslog(LOG_INFO, fmt, ## __VA_ARGS__); \
42                 fprintf(stderr, "%s: "fmt, argv0, ## __VA_ARGS__); \
43         } while (0)
44
45 #define ERROR(fmt, ...) do { \
46                 syslog(LOG_ERR, fmt, ## __VA_ARGS__); \
47                 fprintf(stderr, "%s: "fmt, argv0, ## __VA_ARGS__); \
48         } while (0)
49
50 enum {
51         FS_NONE,
52         FS_JFFS2,
53         FS_DEADCODE,
54 };
55
56 static const char *argv0;
57
58 /* this is a raw syscall - man 2 pivot_root */
59 extern int pivot_root(const char *new_root, const char *put_old);
60
61 static int debug = 0;
62
63 static void foreachdir(const char *dir, int (*cb)(const char*))
64 {
65         char globdir[256];
66         glob_t gl;
67         int j;
68
69         if (dir[strlen(dir) - 1] == '/')
70                 snprintf(globdir, 256, "%s*", dir);
71         else
72                 snprintf(globdir, 256, "%s/*", dir);
73
74         if (!glob(globdir, GLOB_NOESCAPE | GLOB_MARK | GLOB_ONLYDIR, NULL, &gl))
75                 for (j = 0; j < gl.gl_pathc; j++)
76                         foreachdir(gl.gl_pathv[j], cb);
77
78         cb(dir);
79 }
80
81 static char* find_mount_point(char *block, char *fs)
82 {
83         FILE *fp = fopen("/proc/mounts", "r");
84         static char line[256];
85         int len = strlen(block);
86         char *point = NULL;
87
88         if(!fp)
89                 return NULL;
90
91         while (fgets(line, sizeof(line), fp)) {
92                 if (!strncmp(line, block, len)) {
93                         char *p = &line[len + 1];
94                         char *t = strstr(p, " ");
95
96                         if (!t)
97                                 return NULL;
98
99                         *t = '\0';
100                         t++;
101
102                         if (fs && strncmp(t, fs, strlen(fs))) {
103                                 ERROR("block is mounted with wrong fs\n");
104                                 return NULL;
105                         }
106                         point = p;
107                         break;
108                 }
109         }
110
111         fclose(fp);
112
113         return point;
114 }
115
116 static char* find_mtd_index(char *name)
117 {
118         FILE *fp = fopen("/proc/mtd", "r");
119         static char line[256];
120         char *index = NULL;
121
122         if(!fp)
123                 return index;
124
125         while (!index && fgets(line, sizeof(line), fp)) {
126                 if (strstr(line, name)) {
127                         char *eol = strstr(line, ":");
128
129                         if (!eol)
130                                 continue;
131
132                         *eol = '\0';
133                         index = &line[3];
134                         DEBUG(1, "found %s -> index:%s\n", name, index);
135                 }
136         }
137
138         fclose(fp);
139
140         return index;
141 }
142
143 static int find_mtd_block(char *name, char *part, int plen)
144 {
145         char *index = find_mtd_index(name);
146
147         if (!index)
148                 return -1;
149
150         snprintf(part, plen, "/dev/mtdblock%s", index);
151         DEBUG(1, "found %s -> %s\n", name, part);
152
153         return 0;
154 }
155
156 static int find_mtd_char(char *name, char *part, int plen)
157 {
158         char *index = find_mtd_index(name);
159
160         if (!index)
161                 return -1;
162
163         snprintf(part, plen, "/dev/mtd%s", index);
164         DEBUG(1, "found %s -> %s\n", name, part);
165
166         return 0;
167 }
168
169 static int mtd_unlock(char *mtd)
170 {
171         struct erase_info_user mtdlock;
172         struct mtd_info_user mtdinfo;
173         int fd = open(mtd, O_RDWR | O_SYNC);
174         int ret = -1;
175
176         DEBUG(1, "%s\n", mtd);
177
178         if (!fd) {
179                 ERROR("failed to open %s: %s\n", mtd, strerror(errno));
180                 return -1;
181         }
182
183         ret = ioctl(fd, MEMGETINFO, &mtdinfo);
184         if (ret) {
185                 ERROR("ioctl(%s, MEMGETINFO) failed: %s\n", mtd, strerror(errno));
186                 goto err_out;
187         }
188
189         mtdlock.start = 0;
190         mtdlock.length = mtdinfo.size;
191         ioctl(fd, MEMUNLOCK, &mtdlock);
192
193 err_out:
194         close(fd);
195
196         return ret;
197 }
198
199 static int mtd_erase(const char *mtd)
200 {
201         int fd = open(mtd, O_RDWR | O_SYNC);
202         struct mtd_info_user i;
203         struct erase_info_user e;
204         int ret;
205
206         if (!fd) {
207                 ERROR("failed to open %s: %s\n", mtd, strerror(errno));
208                 return -1;
209         }
210
211         ret = ioctl(fd, MEMGETINFO, &i);
212         if (ret) {
213                 ERROR("ioctl(%s, MEMGETINFO) failed: %s\n", mtd, strerror(errno));
214                 return -1;
215         }
216
217         e.length = i.erasesize;
218         for (e.start = 0; e.start < i.size; e.start += i.erasesize) {
219                 ioctl(fd, MEMUNLOCK, &e);
220                 if(ioctl(fd, MEMERASE, &e))
221                         ERROR("Failed to erase block on %s at 0x%x\n", mtd, e.start);
222         }
223
224         close(fd);
225         return 0;
226 }
227
228 static int do_mount_jffs2(void)
229 {
230         char rootfs_data[32];
231
232
233         if (mkdir("/tmp/overlay", 0755)) {
234                 ERROR("failed to mkdir /tmp/overlay: %s\n", strerror(errno));
235                 return -1;
236         }
237
238         if (find_mtd_block("rootfs_data", rootfs_data, sizeof(rootfs_data))) {
239                 ERROR("rootfs_data does not exist\n");
240                 return -1;
241         }
242
243         if (mount(rootfs_data, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) {
244                 ERROR("failed to mount -t jffs2 %s /tmp/overlay: %s\n", rootfs_data, strerror(errno));
245                 return -1;
246         }
247
248         find_mtd_char("rootfs_data", rootfs_data, sizeof(rootfs_data));
249
250         return mtd_unlock(rootfs_data);
251 }
252
253 static int jffs2_ready(char *mtd)
254 {
255         FILE *fp = fopen(mtd, "r");
256         __u32 deadc0de;
257         __u16 jffs2;
258         size_t sz;
259
260         if (!fp) {
261                 ERROR("reading %s failed\n", mtd);
262                 exit(-1);
263         }
264
265         sz = fread(&deadc0de, sizeof(deadc0de), 1, fp);
266         fclose(fp);
267
268         if (sz != 1) {
269                 ERROR("reading %s failed: %s\n", mtd, strerror(errno));
270                 exit(-1);
271         }
272
273         deadc0de = __be32_to_cpu(deadc0de);
274         jffs2 = __be16_to_cpu(deadc0de >> 16);
275
276         if (jffs2 == 0x1985) {
277                 LOG("jffs2 is ready\n");
278                 return FS_JFFS2;
279         }
280
281         if (deadc0de == 0xdeadc0de) {
282                 LOG("jffs2 is not ready - marker found\n");
283                 return FS_DEADCODE;
284         }
285
286         ERROR("No jffs2 marker was found\n");
287
288         return FS_NONE;
289 }
290
291 static int check_fs_exists(char *fs)
292 {
293         FILE *fp = fopen("/proc/filesystems", "r");
294         static char line[256];
295         int ret = -1;
296
297         DEBUG(2, "%s\n", fs);
298
299         if (!fp) {
300                 ERROR("opening /proc/filesystems failed: %s\n", strerror(errno));
301                 goto out;
302         }
303
304         while (ret && fgets(line, sizeof(line), fp))
305                 if (strstr(line, fs))
306                         ret = 0;
307
308         fclose(fp);
309
310 out:
311         return ret;
312 }
313
314 static int mount_move(char *oldroot, char *newroot, char *dir)
315 {
316 #ifndef MS_MOVE
317 #define MS_MOVE (1 << 13)
318 #endif
319         struct stat s;
320         char olddir[64];
321         char newdir[64];
322         int ret;
323
324         DEBUG(2, "%s %s\n", oldroot, dir);
325
326         snprintf(olddir, sizeof(olddir), "%s%s", oldroot, dir);
327         snprintf(newdir, sizeof(newdir), "%s%s", newroot, dir);
328
329         if (stat(olddir, &s) || !S_ISDIR(s.st_mode))
330                 return -1;
331
332         if (stat(newdir, &s) || !S_ISDIR(s.st_mode))
333                 return -1;
334
335         ret = mount(olddir, newdir, NULL, MS_NOATIME | MS_MOVE, NULL);
336
337         if (ret)
338                 DEBUG(1, "failed %s %s: %s\n", olddir, newdir, strerror(errno));
339
340         return ret;
341 }
342
343 static int pivot(char *new, char *old)
344 {
345         char pivotdir[64];
346         int ret;
347
348         DEBUG(2, "%s %s\n", new, old);
349
350         if (mount_move("", new, "/proc"))
351                 return -1;
352
353         snprintf(pivotdir, sizeof(pivotdir), "%s%s", new, old);
354
355         ret = pivot_root(new, pivotdir);
356
357         if (ret < 0) {
358                 ERROR("pivot_root failed %s %s: %s\n", new, pivotdir, strerror(errno));
359                 return -1;
360         }
361
362         mount_move(old, "", "/dev");
363         mount_move(old, "", "/tmp");
364         mount_move(old, "", "/sys");
365         mount_move(old, "", "/overlay");
366
367         return 0;
368 }
369
370
371 static int fopivot(char *rw_root, char *ro_root)
372 {
373         char overlay[64], lowerdir[64];
374
375         DEBUG(2, "%s %s\n", rw_root, ro_root);
376
377         if (check_fs_exists("overlay")) {
378                 ERROR("BUG: no suitable fs found\n");
379                 return -1;
380         }
381
382         snprintf(overlay, sizeof(overlay), "overlayfs:%s", rw_root);
383         snprintf(lowerdir, sizeof(lowerdir), "lowerdir=/,upperdir=%s", rw_root);
384
385         if (mount(overlay, "/mnt", "overlayfs", MS_NOATIME, lowerdir)) {
386                 ERROR("mount failed: %s\n", strerror(errno));
387                 return -1;
388         }
389
390         return pivot("/mnt", ro_root);
391 }
392
393 static int ramoverlay(void)
394 {
395         DEBUG(2, "\n");
396
397         mkdir("/tmp/root", 0755);
398         mount("tmpfs", "/tmp/root", "tmpfs", MS_NOATIME, "mode=0755");
399
400         return fopivot("/tmp/root", "/rom");
401 }
402
403 static int switch2jffs(void)
404 {
405         char mtd[32];
406
407         if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
408                 ERROR("no rootfs_data was found\n");
409                 return -1;
410         }
411
412         if (mount(mtd, "/rom/overlay", "jffs2", MS_NOATIME, NULL)) {
413                 ERROR("failed - mount -t jffs2 %s /rom/overlay: %s\n", mtd, strerror(errno));
414                 return -1;
415         }
416
417         if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) {
418                 ERROR("failed - mount -o remount,ro none: %s\n", strerror(errno));
419                 return -1;
420         }
421
422         system("cp -a /tmp/root/* /rom/overlay");
423
424         if (pivot("/rom", "/mnt")) {
425                 ERROR("failed - pivot /rom /mnt: %s\n", strerror(errno));
426                 return -1;
427         }
428
429         if (mount_move("/mnt", "/tmp/root", "")) {
430                 ERROR("failed - mount -o move /mnt /tmp/root %s\n", strerror(errno));
431                 return -1;
432         }
433
434         return fopivot("/overlay", "/rom");
435 }
436
437 static int handle_whiteout(const char *dir)
438 {
439         struct stat s;
440         char link[256];
441         ssize_t sz;
442         struct dirent **namelist;
443         int n;
444
445         n = scandir(dir, &namelist, NULL, NULL);
446
447         if (n < 1)
448                 return -1;
449
450         while (n--) {
451                 char file[256];
452
453                 snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name);
454                 if (!lstat(file, &s) && S_ISLNK(s.st_mode)) {
455                         sz = readlink(file, link, sizeof(link) - 1);
456                         if (sz > 0) {
457                                 char *orig;
458
459                                 link[sz] = '\0';
460                                 orig = strstr(&file[1], "/");
461                                 if (orig && !strcmp(link, "(overlay-whiteout)")) {
462                                         DEBUG(1, "unlinking %s\n", orig);
463                                         unlink(orig);
464                                 }
465                         }
466                 }
467                 free(namelist[n]);
468         }
469         free(namelist);
470
471         return 0;
472 }
473
474 static int main_switch2jffs(int argc, char **argv)
475 {
476         char mtd[32];
477         char *mp;
478         int ret = -1;
479
480         if (check_fs_exists("overlay")) {
481                 ERROR("overlayfs not found\n");
482                 return ret;
483         }
484
485         find_mtd_block("rootfs_data", mtd, sizeof(mtd));
486         mp = find_mount_point(mtd, NULL);
487         if (mp) {
488                 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
489                 return -1;
490         }
491
492         if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
493                 ERROR("no rootfs_data was found\n");
494                 return ret;
495         }
496
497         switch (jffs2_ready(mtd)) {
498         case FS_NONE:
499                 ERROR("no jffs2 marker found\n");
500                 /* fall through */
501
502         case FS_DEADCODE:
503                 ret = switch2jffs();
504                 if (!ret) {
505                         DEBUG(1, "doing fo cleanup\n");
506                         umount2("/tmp/root", MNT_DETACH);
507                         foreachdir("/overlay/", handle_whiteout);
508                 }
509                 break;
510
511         case FS_JFFS2:
512                 ret = do_mount_jffs2();
513                 if (ret)
514                         break;
515                 if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
516                         ERROR("switching to jffs2 failed\n");
517                         ret = -1;
518                 }
519                 break;
520         }
521
522         return ret;
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
630 int main(int argc, char **argv)
631 {
632         char *mp;
633         char mtd[32];
634
635         argv0 = basename(*argv);
636
637         if (!strcmp(basename(*argv), "switch2jffs"))
638                 return main_switch2jffs(argc, argv);
639
640         if (!strcmp(basename(*argv), "jffs2mark"))
641                 return main_jffs2mark(argc, argv);
642
643         if (!strcmp(basename(*argv), "jffs2reset"))
644                 return main_jffs2reset(argc, argv);
645
646         if (find_mtd_char("rootfs_data", mtd, sizeof(mtd))) {
647                 if (!find_mtd_char("rootfs", mtd, sizeof(mtd)))
648                         mtd_unlock(mtd);
649                 LOG("mounting /dev/root\n");
650                 mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0);
651         } else {
652                 switch (jffs2_ready(mtd)) {
653                 case FS_NONE:
654                 case FS_DEADCODE:
655                         return ramoverlay();
656
657                 case FS_JFFS2:
658                         find_mtd_block("rootfs_data", mtd, sizeof(mtd));
659                         mp = find_mount_point(mtd, NULL);
660                         if (mp) {
661                                 LOG("rootfs_data:%s is already mounted as %s\n", mtd, mp);
662                                 return -1;
663                         }
664
665                         do_mount_jffs2();
666                         DEBUG(1, "switching to jffs2\n");
667                         if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
668                                 ERROR("switching to jffs2 failed - fallback to ramoverlay\n");
669                                 return ramoverlay();
670                         }
671                 }
672         }
673
674         return 0;
675 }