initial import of fs-tools package
[project/fstools.git] / backend / snapshot.c
1 /*
2  * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License version 2.1
6  * as published by the Free Software Foundation
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13
14 #include <sys/stat.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <sys/ioctl.h>
18 #include <sys/mount.h>
19 #include <mtd/mtd-user.h>
20
21 #include <glob.h>
22 #include <fcntl.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <libgen.h>
26 #include <unistd.h>
27 #include <string.h>
28
29 #include <libubox/list.h>
30 #include <libubox/blob.h>
31 #include <libubox/md5.h>
32
33 #include "../fs-state.h"
34 #include "../lib/mtd.h"
35
36 #define PATH_MAX        256
37 #define OWRT            0x4f575254
38 #define DATA            0x44415441
39 #define CONF            0x434f4e46
40
41 struct file_header {
42         uint32_t magic;
43         uint32_t type;
44         uint32_t seq;
45         uint32_t length;
46         uint32_t md5[4];
47 };
48
49 static inline int
50 is_config(struct file_header *h)
51 {
52         return ((h->magic == OWRT) && (h->type == CONF));
53 }
54
55 static inline int
56 valid_file_size(int fs)
57 {
58         if ((fs > 8 * 1024 * 1204) || (fs <= 0))
59                 return -1;
60
61         return 0;
62 }
63
64 static void
65 hdr_to_be32(struct file_header *hdr)
66 {
67         uint32_t *h = (uint32_t *) hdr;
68         int i;
69
70         for (i = 0; i < sizeof(struct file_header) / sizeof(uint32_t); i++)
71                 h[i] = cpu_to_be32(h[i]);
72 }
73
74 static void
75 be32_to_hdr(struct file_header *hdr)
76 {
77         uint32_t *h = (uint32_t *) hdr;
78         int i;
79
80         for (i = 0; i < sizeof(struct file_header) / sizeof(uint32_t); i++)
81                 h[i] = be32_to_cpu(h[i]);
82 }
83
84 static int
85 pad_file_size(int size)
86 {
87         int mod;
88
89         size += sizeof(struct file_header);
90         mod = size % erasesize;
91         if (mod) {
92                 size -= mod;
93                 size += erasesize;
94         }
95
96         return size;
97 }
98
99 static int
100 verify_file_hash(char *file, uint32_t *hash)
101 {
102         uint32_t md5[4];
103
104         if (md5sum(file, md5)) {
105                 fprintf(stderr, "failed to generate md5 sum\n");
106                 return -1;
107         }
108
109         if (memcmp(md5, hash, sizeof(md5))) {
110                 fprintf(stderr, "failed to verify hash of %s.\n", file);
111                 return -1;
112         }
113
114         return 0;
115 }
116
117 static int
118 snapshot_next_free(int fd, uint32_t *seq)
119 {
120         struct file_header hdr = { 0 };
121         int block = 0;
122
123         *seq = rand();
124
125         do {
126                 if (mtd_read_buffer(fd, &hdr, block * erasesize, sizeof(struct file_header))) {
127                         fprintf(stderr, "scanning for next free block failed\n");
128                         return 0;
129                 }
130
131                 be32_to_hdr(&hdr);
132
133                 if (hdr.magic != OWRT)
134                         break;
135
136                 if (hdr.type == DATA && !valid_file_size(hdr.length)) {
137                         if (*seq + 1 != hdr.seq && block)
138                                 return block;
139                         *seq = hdr.seq;
140                         block += pad_file_size(hdr.length) / erasesize;
141                 }
142         } while (hdr.type == DATA);
143
144         return block;
145 }
146
147 static int
148 config_find(int fd, struct file_header *conf, struct file_header *sentinel)
149 {
150         uint32_t seq;
151         int i, next = snapshot_next_free(fd, &seq);
152
153         conf->magic = sentinel->magic = 0;
154
155         if (!mtd_read_buffer(fd, conf, next, sizeof(*conf)))
156                 be32_to_hdr(conf);
157
158         for (i = (mtdsize / erasesize) - 1; i > 0; i--) {
159                 if (mtd_read_buffer(fd, sentinel,  i * erasesize, sizeof(*sentinel))) {
160                         fprintf(stderr, "failed to read header\n");
161                         return -1;
162                 }
163                 be32_to_hdr(sentinel);
164
165                 if (sentinel->magic == OWRT && sentinel->type == CONF && !valid_file_size(sentinel->length)) {
166                         if (next == i)
167                                 return -1;
168                         return i;
169                 }
170         }
171
172         return -1;
173 }
174
175 static int
176 snapshot_info(void)
177 {
178         int fd = mtd_load("rootfs_data");
179         struct file_header hdr = { 0 }, conf;
180         int block = 0;
181
182         if (fd < 1)
183                 return -1;
184
185         fprintf(stderr, "sectors:\t%d, erasesize:\t%dK\n", mtdsize / erasesize, erasesize / 1024);
186         do {
187                 if (mtd_read_buffer(fd, &hdr, block * erasesize, sizeof(struct file_header))) {
188                         fprintf(stderr, "scanning for next free block failed\n");
189                         close(fd);
190                         return 0;
191                 }
192
193                 be32_to_hdr(&hdr);
194
195                 if (hdr.magic != OWRT)
196                         break;
197
198                 if (hdr.type == DATA)
199                         fprintf(stderr, "block %d:\tsnapshot entry, size: %d, sectors: %d, sequence: %d\n", block,  hdr.length, pad_file_size(hdr.length) / erasesize, hdr.seq);
200                 else if (hdr.type == CONF)
201                         fprintf(stderr, "block %d:\tvolatile entry, size: %d, sectors: %d, sequence: %d\n", block,  hdr.length, pad_file_size(hdr.length) / erasesize, hdr.seq);
202
203                 if (hdr.type == DATA && !valid_file_size(hdr.length))
204                         block += pad_file_size(hdr.length) / erasesize;
205         } while (hdr.type == DATA);
206         block = config_find(fd, &conf, &hdr);
207         if (block > 0)
208                 fprintf(stderr, "block %d:\tsentinel entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(hdr.length) / erasesize, hdr.seq);
209         close(fd);
210         return 0;
211 }
212
213 static int
214 snapshot_write_file(int fd, int block, char *file, uint32_t seq, uint32_t type)
215 {
216         uint32_t md5[4] = { 0 };
217         struct file_header hdr;
218         struct stat s;
219         char buffer[256];
220         int in = 0, len, offset;
221         int ret = -1;
222
223         if (stat(file, &s) || md5sum(file, md5)) {
224                 fprintf(stderr, "stat failed on %s\n", file);
225                 goto out;
226         }
227
228         if ((block * erasesize) + pad_file_size(s.st_size) > mtdsize) {
229                 fprintf(stderr, "upgrade is too big for the flash\n");
230                 goto out;
231         }
232         mtd_erase(fd, block, (pad_file_size(s.st_size) / erasesize));
233         mtd_erase(fd, block + (pad_file_size(s.st_size) / erasesize), 1);
234
235         hdr.length = s.st_size;
236         hdr.magic = OWRT;
237         hdr.type = type;
238         hdr.seq = seq;
239         memcpy(hdr.md5, md5, sizeof(md5));
240         hdr_to_be32(&hdr);
241
242         if (mtd_write_buffer(fd, &hdr, block * erasesize, sizeof(struct file_header))) {
243                 fprintf(stderr, "failed to write header\n");
244                 goto out;
245         }
246
247         in = open(file, O_RDONLY);
248         if (in < 1) {
249                 fprintf(stderr, "failed to open %s\n", file);
250                 goto out;
251         }
252
253         offset = (block * erasesize) + sizeof(struct file_header);
254
255         while ((len = read(in, buffer, sizeof(buffer))) > 0) {
256                 if (mtd_write_buffer(fd, buffer, offset, len) < 0)
257                         goto out;
258                 offset += len;
259         }
260
261         ret = 0;
262
263 out:
264         if (in > 0)
265                 close(in);
266
267         return ret;
268 }
269
270 static int
271 snapshot_read_file(int fd, int block, char *file, uint32_t type)
272 {
273         struct file_header hdr;
274         char buffer[256];
275         int out;
276
277         if (mtd_read_buffer(fd, &hdr, block * erasesize, sizeof(struct file_header))) {
278                 fprintf(stderr, "failed to read header\n");
279                 return -1;
280         }
281         be32_to_hdr(&hdr);
282
283         if (hdr.magic != OWRT)
284                 return -1;
285
286         if (hdr.type != type)
287                 return -1;
288
289         if (valid_file_size(hdr.length))
290                 return -1;
291
292         out = open(file, O_WRONLY | O_CREAT, 0700);
293         if (!out) {
294                 fprintf(stderr, "failed to open %s\n", file);
295                 return -1;
296         }
297
298         while (hdr.length > 0) {
299                 int len = sizeof(buffer);
300
301                 if (hdr.length < len)
302                         len = hdr.length;
303
304                 if ((read(fd, buffer, len) != len) || (write(out, buffer, len) != len)) {
305                         return -1;
306                 }
307
308                 hdr.length -= len;
309         }
310
311         close(out);
312
313         if (verify_file_hash(file, hdr.md5)) {
314                 fprintf(stderr, "md5 verification failed\n");
315                 unlink(file);
316                 return 0;
317         }
318
319         block += pad_file_size(hdr.length) / erasesize;
320
321         return block;
322 }
323
324 static int
325 sentinel_write(int fd, uint32_t _seq)
326 {
327         int ret, block;
328         struct stat s;
329         uint32_t seq;
330
331         if (stat("/tmp/config.tar.gz", &s)) {
332                 fprintf(stderr, "failed to stat /tmp/config.tar.gz\n");
333                 return -1;
334         }
335
336         snapshot_next_free(fd, &seq);
337         if (_seq)
338                 seq = _seq;
339         block = mtdsize / erasesize;
340         block -= pad_file_size(s.st_size) / erasesize;
341         if (block < 0)
342                 block = 0;
343
344         ret = snapshot_write_file(fd, block, "/tmp/config.tar.gz", seq, CONF);
345         if (ret)
346                 fprintf(stderr, "failed to write sentinel\n");
347         else
348                 fprintf(stderr, "wrote /tmp/config.tar.gz sentinel\n");
349         return ret;
350 }
351
352 static int
353 volatile_write(int fd, uint32_t _seq)
354 {
355         int block, ret;
356         uint32_t seq;
357
358         block = snapshot_next_free(fd, &seq);
359         if (_seq)
360                 seq = _seq;
361         if (block < 0)
362                 block = 0;
363
364         ret = snapshot_write_file(fd, block, "/tmp/config.tar.gz", seq, CONF);
365         if (ret)
366                 fprintf(stderr, "failed to write /tmp/config.tar.gz\n");
367         else
368                 fprintf(stderr, "wrote /tmp/config.tar.gz\n");
369         return ret;
370 }
371
372 static int
373 config_write(int argc, char **argv)
374 {
375         int fd, ret;
376
377         fd = mtd_load("rootfs_data");
378         if (fd < 1) {
379                 fprintf(stderr, "failed to open rootfs_config\n");
380                 return -1;
381         }
382
383         ret = volatile_write(fd, 0);
384         if (!ret)
385                 ret = sentinel_write(fd, 0);
386
387         close(fd);
388
389         return ret;
390 }
391
392 static int
393 config_read(int argc, char **argv)
394 {
395         struct file_header conf, sentinel;
396         int fd, next, block, ret = 0;
397         uint32_t seq;
398
399         fd = mtd_load("rootfs_data");
400         if (fd < 1) {
401                 fprintf(stderr, "failed to open rootfs_data\n");
402                 return -1;
403         }
404
405         block = config_find(fd, &conf, &sentinel);
406         next = snapshot_next_free(fd, &seq);
407         if (is_config(&conf) && conf.seq == seq)
408                 block = next;
409         else if (!is_config(&sentinel) || sentinel.seq != seq)
410                 return -1;
411
412         unlink("/tmp/config.tar.gz");
413         ret = snapshot_read_file(fd, block, "/tmp/config.tar.gz", CONF);
414
415         if (ret < 1)
416                 fprintf(stderr, "failed to read /tmp/config.tar.gz\n");
417         close(fd);
418         return ret;
419 }
420
421 static int
422 snapshot_write(int argc, char **argv)
423 {
424         int mtd, block, ret;
425         uint32_t seq;
426
427         mtd = mtd_load("rootfs_data");
428         if (mtd < 1) {
429                 fprintf(stderr, "failed to open rootfs_data\n");
430                 return -1;
431         }
432
433         block = snapshot_next_free(mtd, &seq);
434         if (block < 0)
435                 block = 0;
436
437         ret = snapshot_write_file(mtd, block, "/tmp/snapshot.tar.gz", seq + 1, DATA);
438         if (ret)
439                 fprintf(stderr, "failed to write /tmp/snapshot.tar.gz\n");
440         else
441                 fprintf(stderr, "wrote /tmp/snapshot.tar.gz\n");
442
443         close(mtd);
444
445         return ret;
446 }
447
448 static int
449 snapshot_mark(int argc, char **argv)
450 {
451         FILE *fp;
452         __be32 owrt = cpu_to_be32(OWRT);
453         char mtd[32];
454         size_t sz;
455
456         fprintf(stderr, "This will remove all snapshot data stored on the system. Are you sure? [N/y]\n");
457         if (getchar() != 'y')
458                 return -1;
459
460         if (find_mtd_block("rootfs_data", mtd, sizeof(mtd))) {
461                 fprintf(stderr, "no rootfs_data was found\n");
462                 return -1;
463         }
464
465         fp = fopen(mtd, "w");
466         fprintf(stderr, "%s - marking with 0x4f575254\n", mtd);
467         if (!fp) {
468                 fprintf(stderr, "opening %s failed\n", mtd);
469                 return -1;
470         }
471
472         sz = fwrite(&owrt, sizeof(owrt), 1, fp);
473         fclose(fp);
474
475         if (sz != 1) {
476                 fprintf(stderr, "writing %s failed: %s\n", mtd, strerror(errno));
477                 return -1;
478         }
479
480         return 0;
481 }
482
483 static int
484 snapshot_read(int argc, char **argv)
485 {
486         char file[64];
487         int block = 0, fd, ret = 0;
488
489         fd = mtd_load("rootfs_data");
490         if (fd < 1) {
491                 fprintf(stderr, "failed to open rootfs_data\n");
492                 return -1;
493         }
494
495         if (argc > 1) {
496                 block = atoi(argv[1]);
497                 if (block >= (mtdsize / erasesize)) {
498                         fprintf(stderr, "invalid block %d > %d\n", block, mtdsize / erasesize);
499                         goto out;
500                 }
501                 snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block);
502
503                 ret = snapshot_read_file(fd, block, file, DATA);
504                 goto out;
505         }
506
507         do {
508                 snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block);
509                 block = snapshot_read_file(fd, block, file, DATA);
510         } while (block > 0);
511
512 out:
513         close(fd);
514         return ret;
515 }
516
517 static int
518 snapshot_sync(void)
519 {
520         int fd = mtd_load("rootfs_data");
521         struct file_header sentinel, conf;
522         int next, block = 0;
523         uint32_t seq;
524
525         if (fd < 1)
526                 return -1;
527
528         next = snapshot_next_free(fd, &seq);
529         block = config_find(fd, &conf, &sentinel);
530         if (is_config(&conf) && conf.seq != seq) {
531                 conf.magic = 0;
532                 mtd_erase(fd, next, 2);
533         }
534
535         if (is_config(&sentinel) && (sentinel.seq != seq)) {
536                 sentinel.magic = 0;
537                 mtd_erase(fd, block, 1);
538         }
539
540         if (!is_config(&conf) && !is_config(&sentinel)) {
541         //      fprintf(stderr, "no config found\n");
542         } else if (((is_config(&conf) && is_config(&sentinel)) &&
543                                 (memcmp(conf.md5, sentinel.md5, sizeof(conf.md5)) || (conf.seq != sentinel.seq))) ||
544                         (is_config(&conf) && !is_config(&sentinel))) {
545                 uint32_t seq;
546                 int next = snapshot_next_free(fd, &seq);
547                 int ret = snapshot_read_file(fd, next, "/tmp/config.tar.gz", CONF);
548                 if (ret > 0) {
549                         if (sentinel_write(fd, conf.seq))
550                                 fprintf(stderr, "failed to write sentinel data");
551                 }
552         } else if (!is_config(&conf) && is_config(&sentinel) && next) {
553                 int ret = snapshot_read_file(fd, block, "/tmp/config.tar.gz", CONF);
554                 if (ret > 0)
555                         if (volatile_write(fd, sentinel.seq))
556                                 fprintf(stderr, "failed to write sentinel data");
557         } else
558                 fprintf(stderr, "config in sync\n");
559
560         unlink("/tmp/config.tar.gz");
561         close(fd);
562
563         return 0;
564 }
565
566 static int
567 _ramoverlay(char *rom, char *overlay)
568 {
569         mount("tmpfs", overlay, "tmpfs", MS_NOATIME, "mode=0755");
570         return fopivot(overlay, rom);
571 }
572
573 static int
574 snapshot_mount(void)
575 {
576         snapshot_sync();
577         setenv("SNAPSHOT", "magic", 1);
578         _ramoverlay("/rom", "/overlay");
579         system("/sbin/snapshot unpack");
580         foreachdir("/overlay/", handle_whiteout);
581         mkdir("/volatile", 0700);
582         _ramoverlay("/rom", "/volatile");
583         mount_move("/rom/volatile", "/volatile", "");
584         mount_move("/rom/rom", "/rom", "");
585         system("/sbin/snapshot config_unpack");
586         foreachdir("/volatile/", handle_whiteout);
587         unsetenv("SNAPSHOT");
588         return -1;
589 }
590
591 static struct backend_handler snapshot_handlers[] = {
592 {
593         .name = "config_read",
594         .cli = config_read,
595 }, {
596         .name = "config_write",
597         .cli = config_write,
598 }, {
599         .name = "read",
600         .cli = snapshot_read,
601 }, {
602         .name = "write",
603         .cli = snapshot_write,
604 }, {
605         .name = "mark",
606         .cli = snapshot_mark,
607 }};
608
609 static struct backend snapshot_backend = {
610         .name = "snapshot",
611         .num_handlers = ARRAY_SIZE(snapshot_handlers),
612         .handlers = snapshot_handlers,
613         .mount = snapshot_mount,
614         .info = snapshot_info,
615 };
616 BACKEND(snapshot_backend);