05a6dee577978c2fe63f6e82a66f7f0aaecdde11
[project/fstools.git] / 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 "libfstools/libfstools.h"
34 #include "libfstools/volume.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(struct volume *v, int size)
86 {
87         int mod;
88
89         size += sizeof(struct file_header);
90         mod = size % v->block_size;
91         if (mod) {
92                 size -= mod;
93                 size += v->block_size;
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(struct volume *v, uint32_t *seq)
119 {
120         struct file_header hdr = { 0 };
121         int block = 0;
122
123         *seq = rand();
124
125         do {
126                 if (volume_read(v, &hdr, block * v->block_size, 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(v, hdr.length) / v->block_size;
141                 }
142         } while (hdr.type == DATA);
143
144         return block;
145 }
146
147 static int
148 config_find(struct volume *v, struct file_header *conf, struct file_header *sentinel)
149 {
150         uint32_t seq;
151         int i, next = snapshot_next_free(v, &seq);
152
153         conf->magic = sentinel->magic = 0;
154
155         if (!volume_read(v, conf, next, sizeof(*conf)))
156                 be32_to_hdr(conf);
157
158         for (i = (v->size / v->block_size) - 1; i > 0; i--) {
159                 if (volume_read(v, sentinel,  i * v->block_size, 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_write_file(struct volume *v, int block, char *file, uint32_t seq, uint32_t type)
177 {
178         uint32_t md5[4] = { 0 };
179         struct file_header hdr;
180         struct stat s;
181         char buffer[256];
182         int in = 0, len, offset;
183         int ret = -1;
184
185         if (stat(file, &s) || md5sum(file, md5)) {
186                 fprintf(stderr, "stat failed on %s\n", file);
187                 goto out;
188         }
189
190         if ((block * v->block_size) + pad_file_size(v, s.st_size) > v->size) {
191                 fprintf(stderr, "upgrade is too big for the flash\n");
192                 goto out;
193         }
194         volume_erase(v, block * v->block_size, pad_file_size(v, s.st_size));
195         volume_erase(v, block * v->block_size + pad_file_size(v, s.st_size), v->block_size);
196
197         hdr.length = s.st_size;
198         hdr.magic = OWRT;
199         hdr.type = type;
200         hdr.seq = seq;
201         memcpy(hdr.md5, md5, sizeof(md5));
202         hdr_to_be32(&hdr);
203
204         if (volume_write(v, &hdr, block * v->block_size, sizeof(struct file_header))) {
205                 fprintf(stderr, "failed to write header\n");
206                 goto out;
207         }
208
209         in = open(file, O_RDONLY);
210         if (in < 1) {
211                 fprintf(stderr, "failed to open %s\n", file);
212                 goto out;
213         }
214
215         offset = (block * v->block_size) + sizeof(struct file_header);
216
217         while ((len = read(in, buffer, sizeof(buffer))) > 0) {
218                 if (volume_write(v, buffer, offset, len) < 0)
219                         goto out;
220                 offset += len;
221         }
222
223         ret = 0;
224
225 out:
226         if (in > 0)
227                 close(in);
228
229         return ret;
230 }
231
232 static int
233 snapshot_read_file(struct volume *v, int block, char *file, uint32_t type)
234 {
235         struct file_header hdr;
236         char buffer[256];
237         int out, offset = 0;
238
239         if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) {
240                 fprintf(stderr, "failed to read header\n");
241                 return -1;
242         }
243         be32_to_hdr(&hdr);
244
245         if (hdr.magic != OWRT)
246                 return -1;
247
248         if (hdr.type != type)
249                 return -1;
250
251         if (valid_file_size(hdr.length))
252                 return -1;
253
254         out = open(file, O_WRONLY | O_CREAT, 0700);
255         if (!out) {
256                 fprintf(stderr, "failed to open %s\n", file);
257                 return -1;
258         }
259
260         while (hdr.length > 0) {
261                 int len = sizeof(buffer);
262
263                 if (hdr.length < len)
264                         len = hdr.length;
265
266                 if ((volume_read(v, buffer, offset, len) != len) || (write(out, buffer, len) != len))
267                         return -1;
268
269                 offset += len;
270                 hdr.length -= len;
271         }
272
273         close(out);
274
275         if (verify_file_hash(file, hdr.md5)) {
276                 fprintf(stderr, "md5 verification failed\n");
277                 unlink(file);
278                 return 0;
279         }
280
281         block += pad_file_size(v, hdr.length) / v->block_size;
282
283         return block;
284 }
285
286 static int
287 sentinel_write(struct volume *v, uint32_t _seq)
288 {
289         int ret, block;
290         struct stat s;
291         uint32_t seq;
292
293         if (stat("/tmp/config.tar.gz", &s)) {
294                 fprintf(stderr, "failed to stat /tmp/config.tar.gz\n");
295                 return -1;
296         }
297
298         snapshot_next_free(v, &seq);
299         if (_seq)
300                 seq = _seq;
301         block = v->size / v->block_size;
302         block -= pad_file_size(v, s.st_size) / v->block_size;
303         if (block < 0)
304                 block = 0;
305
306         ret = snapshot_write_file(v, block, "/tmp/config.tar.gz", seq, CONF);
307         if (ret)
308                 fprintf(stderr, "failed to write sentinel\n");
309         else
310                 fprintf(stderr, "wrote /tmp/config.tar.gz sentinel\n");
311         return ret;
312 }
313
314 static int
315 volatile_write(struct volume *v, uint32_t _seq)
316 {
317         int block, ret;
318         uint32_t seq;
319
320         block = snapshot_next_free(v, &seq);
321         if (_seq)
322                 seq = _seq;
323         if (block < 0)
324                 block = 0;
325
326         ret = snapshot_write_file(v, block, "/tmp/config.tar.gz", seq, CONF);
327         if (ret)
328                 fprintf(stderr, "failed to write /tmp/config.tar.gz\n");
329         else
330                 fprintf(stderr, "wrote /tmp/config.tar.gz\n");
331         return ret;
332 }
333
334 static int
335 config_write(int argc, char *argv[1])
336 {
337         struct volume *v = volume_find("rootfs_data");
338         int ret;
339
340         if (!v)
341                 return -1;
342
343         ret = volatile_write(v, 0);
344         if (!ret)
345                 ret = sentinel_write(v, 0);
346
347         return ret;
348 }
349
350 static int
351 config_read(int argc, char *argv[1])
352 {
353         struct volume *v = volume_find("rootfs_data");
354         struct file_header conf, sentinel;
355         int next, block, ret = 0;
356         uint32_t seq;
357
358         if (!v)
359                 return -1;
360
361         block = config_find(v, &conf, &sentinel);
362         next = snapshot_next_free(v, &seq);
363         if (is_config(&conf) && conf.seq == seq)
364                 block = next;
365         else if (!is_config(&sentinel) || sentinel.seq != seq)
366                 return -1;
367
368         unlink("/tmp/config.tar.gz");
369         ret = snapshot_read_file(v, block, "/tmp/config.tar.gz", CONF);
370
371         if (ret < 1)
372                 fprintf(stderr, "failed to read /tmp/config.tar.gz\n");
373
374         return ret;
375 }
376
377 static int
378 snapshot_write(int argc, char *argv[1])
379 {
380         struct volume *v = volume_find("rootfs_data");
381         int block, ret;
382         uint32_t seq;
383
384         if (!v)
385                 return -1;
386
387         block = snapshot_next_free(v, &seq);
388         if (block < 0)
389                 block = 0;
390
391         ret = snapshot_write_file(v, block, "/tmp/snapshot.tar.gz", seq + 1, DATA);
392         if (ret)
393                 fprintf(stderr, "failed to write /tmp/snapshot.tar.gz\n");
394         else
395                 fprintf(stderr, "wrote /tmp/snapshot.tar.gz\n");
396
397         return ret;
398 }
399
400 static int
401 snapshot_mark(int argc, char *argv[1])
402 {
403         __be32 owrt = cpu_to_be32(OWRT);
404         struct volume *v;
405         size_t sz;
406         int fd;
407
408         fprintf(stderr, "This will remove all snapshot data stored on the system. Are you sure? [N/y]\n");
409         if (getchar() != 'y')
410                 return -1;
411
412         v = volume_find("rootfs_data");
413         if (!v) {
414                 fprintf(stderr, "no rootfs_data was found\n");
415                 return -1;
416         }
417
418         fd = open(v->blk, O_WRONLY);
419         fprintf(stderr, "%s - marking with 0x%08x\n", v->blk, owrt);
420         if (fd < 0) {
421                 fprintf(stderr, "opening %s failed\n", v->blk);
422                 return -1;
423         }
424
425         sz = write(fd, &owrt, sizeof(owrt));
426         close(fd);
427
428         if (sz != 1) {
429                 fprintf(stderr, "writing %s failed: %s\n", v->blk, strerror(errno));
430                 return -1;
431         }
432
433         return 0;
434 }
435
436 static int
437 snapshot_read(int argc, char *argv[1])
438 {
439         struct volume *v = volume_find("rootfs_data");;
440         int block = 0, ret = 0;
441         char file[64];
442
443         if (!v)
444                 return -1;
445
446         if (argc > 1) {
447                 block = atoi(argv[1]);
448                 if (block >= (v->size / v->block_size)) {
449                         fprintf(stderr, "invalid block %d > %llu\n", block, v->size / v->block_size);
450                         goto out;
451                 }
452                 snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block);
453
454                 ret = snapshot_read_file(v, block, file, DATA);
455                 goto out;
456         }
457
458         do {
459                 snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block);
460                 block = snapshot_read_file(v, block, file, DATA);
461         } while (block > 0);
462
463 out:
464         return ret;
465 }
466
467 int main(int argc, char **argv)
468 {
469         if (argc < 2)
470                 return -1;
471
472         if (!strcmp(argv[1], "config_read"))
473                 return config_read(argc, argv);
474         if (!strcmp(argv[1], "config_write"))
475                 return config_write(argc, argv);
476         if (!strcmp(argv[1], "read"))
477                 return snapshot_read(argc, argv);
478         if (!strcmp(argv[1], "write"))
479                 return snapshot_write(argc, argv);
480         if (!strcmp(argv[1], "mark"))
481                 return snapshot_mark(argc, argv);
482         return -1;
483 }