Btrfs-progs: replace find_mount_root from send code
[platform/upstream/btrfs-progs.git] / cmds-send.c
1 /*
2  * Copyright (C) 2012 Alexander Block.  All rights reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License v2 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 GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public
14  * License along with this program; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 021110-1307, USA.
17  */
18
19 #define _GNU_SOURCE
20
21 #include <unistd.h>
22 #include <stdint.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <pthread.h>
26 #include <math.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/ioctl.h>
30 #include <libgen.h>
31 #include <mntent.h>
32
33 #include <uuid/uuid.h>
34
35 #include "ctree.h"
36 #include "ioctl.h"
37 #include "commands.h"
38 #include "list.h"
39
40 #include "send.h"
41 #include "send-utils.h"
42
43 static int g_verbose = 0;
44
45 struct btrfs_send {
46         int send_fd;
47         int dump_fd;
48         int mnt_fd;
49
50         u64 *clone_sources;
51         u64 clone_sources_count;
52
53         char *root_path;
54         struct subvol_uuid_search sus;
55 };
56
57 int find_mount_root(const char *path, char **mount_root)
58 {
59         FILE *mnttab;
60         int fd;
61         struct mntent *ent;
62         int len;
63         int longest_matchlen = 0;
64         char *longest_match = NULL;
65
66         fd = open(path, O_RDONLY | O_NOATIME);
67         if (fd < 0)
68                 return -errno;
69         close(fd);
70
71         mnttab = fopen("/etc/mtab", "r");
72         while ((ent = getmntent(mnttab))) {
73                 len = strlen(ent->mnt_dir);
74                 if (strncmp(ent->mnt_dir, path, len) == 0) {
75                         /* match found */
76                         if (longest_matchlen < len) {
77                                 free(longest_match);
78                                 longest_matchlen = len;
79                                 longest_match = strdup(ent->mnt_dir);
80                         }
81                 }
82         }
83
84         *mount_root = realpath(longest_match, NULL);
85         free(longest_match);
86
87         return 0;
88 }
89
90 static int get_root_id(struct btrfs_send *s, const char *path, u64 *root_id)
91 {
92         struct subvol_info *si;
93
94         si = subvol_uuid_search(&s->sus, 0, NULL, 0, path,
95                         subvol_search_by_path);
96         if (!si)
97                 return -ENOENT;
98         *root_id = si->root_id;
99         return 0;
100 }
101
102 static struct subvol_info *get_parent(struct btrfs_send *s, u64 root_id)
103 {
104         struct subvol_info *si;
105
106         si = subvol_uuid_search(&s->sus, root_id, NULL, 0, NULL,
107                         subvol_search_by_root_id);
108         if (!si)
109                 return NULL;
110
111         si = subvol_uuid_search(&s->sus, 0, si->parent_uuid, 0, NULL,
112                         subvol_search_by_uuid);
113         if (!si)
114                 return NULL;
115         return si;
116 }
117
118 static int find_good_parent(struct btrfs_send *s, u64 root_id, u64 *found)
119 {
120         int ret;
121         struct subvol_info *parent;
122         struct subvol_info *parent2;
123         struct subvol_info *best_parent = NULL;
124         __s64 tmp;
125         u64 best_diff = (u64)-1;
126         int i;
127
128         parent = get_parent(s, root_id);
129         if (!parent) {
130                 ret = -ENOENT;
131                 goto out;
132         }
133
134         for (i = 0; i < s->clone_sources_count; i++) {
135                 if (s->clone_sources[i] == parent->root_id) {
136                         best_parent = parent;
137                         goto out_found;
138                 }
139         }
140
141         for (i = 0; i < s->clone_sources_count; i++) {
142                 parent2 = get_parent(s, s->clone_sources[i]);
143                 if (parent2 != parent)
144                         continue;
145
146                 parent2 = subvol_uuid_search(&s->sus, s->clone_sources[i], NULL,
147                                 0, NULL, subvol_search_by_root_id);
148
149                 tmp = parent2->ctransid - parent->ctransid;
150                 if (tmp < 0)
151                         tmp *= -1;
152                 if (tmp < best_diff) {
153                         best_parent = parent;
154                         best_diff = tmp;
155                 }
156         }
157
158         if (!best_parent) {
159                 ret = -ENOENT;
160                 goto out;
161         }
162
163 out_found:
164         *found = best_parent->root_id;
165         ret = 0;
166
167 out:
168         return ret;
169 }
170
171 static void add_clone_source(struct btrfs_send *s, u64 root_id)
172 {
173         s->clone_sources = realloc(s->clone_sources,
174                 sizeof(*s->clone_sources) * (s->clone_sources_count + 1));
175         s->clone_sources[s->clone_sources_count++] = root_id;
176 }
177
178 static int write_buf(int fd, const void *buf, int size)
179 {
180         int ret;
181         int pos = 0;
182
183         while (pos < size) {
184                 ret = write(fd, (char*)buf + pos, size - pos);
185                 if (ret < 0) {
186                         ret = -errno;
187                         fprintf(stderr, "ERROR: failed to dump stream. %s",
188                                         strerror(-ret));
189                         goto out;
190                 }
191                 if (!ret) {
192                         ret = -EIO;
193                         fprintf(stderr, "ERROR: failed to dump stream. %s",
194                                         strerror(-ret));
195                         goto out;
196                 }
197                 pos += ret;
198         }
199         ret = 0;
200
201 out:
202         return ret;
203 }
204
205 static void *dump_thread(void *arg_)
206 {
207         int ret;
208         struct btrfs_send *s = (struct btrfs_send*)arg_;
209         char buf[4096];
210         int readed;
211
212         while (1) {
213                 readed = read(s->send_fd, buf, sizeof(buf));
214                 if (readed < 0) {
215                         ret = -errno;
216                         fprintf(stderr, "ERROR: failed to read stream from "
217                                         "kernel. %s\n", strerror(-ret));
218                         goto out;
219                 }
220                 if (!readed) {
221                         ret = 0;
222                         goto out;
223                 }
224                 ret = write_buf(s->dump_fd, buf, readed);
225                 if (ret < 0)
226                         goto out;
227         }
228
229 out:
230         if (ret < 0) {
231                 exit(-ret);
232         }
233
234         return ERR_PTR(ret);
235 }
236
237 static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root)
238 {
239         int ret;
240         pthread_t t_read;
241         pthread_attr_t t_attr;
242         struct btrfs_ioctl_send_args io_send;
243         struct subvol_info *si;
244         void *t_err = NULL;
245         int subvol_fd = -1;
246         int pipefd[2];
247
248         si = subvol_uuid_search(&send->sus, root_id, NULL, 0, NULL,
249                         subvol_search_by_root_id);
250         if (!si) {
251                 ret = -ENOENT;
252                 fprintf(stderr, "ERROR: could not find subvol info for %llu",
253                                 root_id);
254                 goto out;
255         }
256
257         subvol_fd = openat(send->mnt_fd, si->path, O_RDONLY | O_NOATIME);
258         if (subvol_fd < 0) {
259                 ret = -errno;
260                 fprintf(stderr, "ERROR: open %s failed. %s\n", si->path,
261                                 strerror(-ret));
262                 goto out;
263         }
264
265         ret = pthread_attr_init(&t_attr);
266
267         ret = pipe(pipefd);
268         if (ret < 0) {
269                 ret = -errno;
270                 fprintf(stderr, "ERROR: pipe failed. %s\n", strerror(-ret));
271                 goto out;
272         }
273
274         io_send.send_fd = pipefd[1];
275         send->send_fd = pipefd[0];
276
277         if (!ret)
278                 ret = pthread_create(&t_read, &t_attr, dump_thread,
279                                         send);
280         if (ret) {
281                 ret = -ret;
282                 fprintf(stderr, "ERROR: thread setup failed: %s\n",
283                         strerror(-ret));
284                 goto out;
285         }
286
287         io_send.clone_sources = (__u64*)send->clone_sources;
288         io_send.clone_sources_count = send->clone_sources_count;
289         io_send.parent_root = parent_root;
290         ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send);
291         if (ret) {
292                 ret = -errno;
293                 fprintf(stderr, "ERROR: send ioctl failed with %d: %s\n", ret,
294                         strerror(-ret));
295                 goto out;
296         }
297         if (g_verbose > 0)
298                 fprintf(stderr, "BTRFS_IOC_SEND returned %d\n", ret);
299
300         if (g_verbose > 0)
301                 fprintf(stderr, "joining genl thread\n");
302
303         close(pipefd[1]);
304         pipefd[1] = 0;
305
306         ret = pthread_join(t_read, &t_err);
307         if (ret) {
308                 ret = -ret;
309                 fprintf(stderr, "ERROR: pthread_join failed: %s\n",
310                         strerror(-ret));
311                 goto out;
312         }
313         if (t_err) {
314                 ret = (long int)t_err;
315                 fprintf(stderr, "ERROR: failed to process send stream, ret=%ld "
316                         "(%s)\n", (long int)t_err, strerror(-ret));
317                 goto out;
318         }
319
320         pthread_attr_destroy(&t_attr);
321
322         ret = 0;
323
324 out:
325         if (subvol_fd != -1)
326                 close(subvol_fd);
327         if (pipefd[0])
328                 close(pipefd[0]);
329         if (pipefd[1])
330                 close(pipefd[1]);
331         return ret;
332 }
333
334 static const char *get_subvol_name(struct btrfs_send *s, const char *full_path)
335 {
336         int len = strlen(s->root_path);
337         if (!len)
338                 return full_path;
339         if (s->root_path[len - 1] != '/')
340                 len += 1;
341
342         return full_path + len;
343 }
344
345 static int init_root_path(struct btrfs_send *s, const char *subvol)
346 {
347         int ret = 0;
348
349         if (s->root_path)
350                 goto out;
351
352         ret = find_mount_root(subvol, &s->root_path);
353         if (ret < 0) {
354                 ret = -EINVAL;
355                 fprintf(stderr, "ERROR: failed to determine mount point "
356                                 "for %s\n", subvol);
357                 goto out;
358         }
359
360         s->mnt_fd = open(s->root_path, O_RDONLY | O_NOATIME);
361         if (s->mnt_fd < 0) {
362                 ret = -errno;
363                 fprintf(stderr, "ERROR: can't open '%s': %s\n", s->root_path,
364                         strerror(-ret));
365                 goto out;
366         }
367
368         ret = subvol_uuid_search_init(s->mnt_fd, &s->sus);
369         if (ret < 0) {
370                 fprintf(stderr, "ERROR: failed to initialize subvol search. "
371                                 "%s\n", strerror(-ret));
372                 goto out;
373         }
374
375 out:
376         return ret;
377
378 }
379
380 static int is_subvol_ro(struct btrfs_send *s, char *subvol)
381 {
382         int ret;
383         u64 flags;
384         int fd = -1;
385
386         fd = openat(s->mnt_fd, subvol, O_RDONLY | O_NOATIME);
387         if (fd < 0) {
388                 ret = -errno;
389                 fprintf(stderr, "ERROR: failed to open %s. %s\n",
390                                 subvol, strerror(-ret));
391                 goto out;
392         }
393
394         ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
395         if (ret < 0) {
396                 ret = -errno;
397                 fprintf(stderr, "ERROR: failed to get flags for subvolume. "
398                                 "%s\n", strerror(-ret));
399                 goto out;
400         }
401
402         if (flags & BTRFS_SUBVOL_RDONLY)
403                 ret = 1;
404         else
405                 ret = 0;
406
407 out:
408         if (fd != -1)
409                 close(fd);
410
411         return ret;
412 }
413
414 int cmd_send_start(int argc, char **argv)
415 {
416         char *subvol = NULL;
417         char c;
418         int ret;
419         char *outname = NULL;
420         struct btrfs_send send;
421         u32 i;
422         char *mount_root = NULL;
423         char *snapshot_parent = NULL;
424         u64 root_id;
425         u64 parent_root_id = 0;
426
427         memset(&send, 0, sizeof(send));
428         send.dump_fd = fileno(stdout);
429
430         while ((c = getopt(argc, argv, "vf:i:p:")) != -1) {
431                 switch (c) {
432                 case 'v':
433                         g_verbose++;
434                         break;
435                 case 'i': {
436                         subvol = realpath(optarg, NULL);
437                         if (!subvol) {
438                                 ret = -errno;
439                                 fprintf(stderr, "ERROR: realpath %s failed. "
440                                                 "%s\n", optarg, strerror(-ret));
441                                 goto out;
442                         }
443
444                         ret = init_root_path(&send, subvol);
445                         if (ret < 0)
446                                 goto out;
447
448                         ret = get_root_id(&send, get_subvol_name(&send, subvol),
449                                         &root_id);
450                         if (ret < 0) {
451                                 fprintf(stderr, "ERROR: could not resolve "
452                                                 "root_id for %s\n", subvol);
453                                 goto out;
454                         }
455                         add_clone_source(&send, root_id);
456                         free(subvol);
457                         break;
458                 }
459                 case 'f':
460                         outname = optarg;
461                         break;
462                 case 'p':
463                         snapshot_parent = realpath(optarg, NULL);
464                         if (!snapshot_parent) {
465                                 ret = -errno;
466                                 fprintf(stderr, "ERROR: realpath %s failed. "
467                                                 "%s\n", optarg, strerror(-ret));
468                                 goto out;
469                         }
470                         break;
471                 case '?':
472                 default:
473                         fprintf(stderr, "ERROR: send args invalid.\n");
474                         return 1;
475                 }
476         }
477
478         if (optind == argc) {
479                 fprintf(stderr, "ERROR: send needs path to snapshot\n");
480                 return 1;
481         }
482
483         if (outname != NULL) {
484                 send.dump_fd = creat(outname, 0600);
485                 if (send.dump_fd == -1) {
486                         ret = -errno;
487                         fprintf(stderr, "ERROR: can't create '%s': %s\n",
488                                         outname, strerror(-ret));
489                         goto out;
490                 }
491         }
492
493         /* use first send subvol to determine mount_root */
494         subvol = argv[optind];
495
496         subvol = realpath(argv[optind], NULL);
497         if (!subvol) {
498                 fprintf(stderr, "ERROR: unable to resolve %s\n", argv[optind]);
499                 goto out;
500         }
501
502         ret = init_root_path(&send, subvol);
503         if (ret < 0)
504                 goto out;
505
506         if (snapshot_parent != NULL) {
507                 ret = get_root_id(&send,
508                                 get_subvol_name(&send, snapshot_parent),
509                                 &parent_root_id);
510                 if (ret < 0) {
511                         fprintf(stderr, "ERROR: could not resolve root_id "
512                                         "for %s\n", snapshot_parent);
513                         goto out;
514                 }
515
516                 add_clone_source(&send, parent_root_id);
517         }
518
519         for (i = optind; i < argc; i++) {
520                 subvol = realpath(argv[i], NULL);
521                 if (!subvol) {
522                         fprintf(stderr, "ERROR: unable to resolve %s\n", argv[i]);
523                         goto out;
524                 }
525
526                 ret = find_mount_root(subvol, &mount_root);
527                 if (ret < 0) {
528                         fprintf(stderr, "ERROR: find_mount_root failed on %s: "
529                                         "%s\n", subvol,
530                                 strerror(-ret));
531                         goto out;
532                 }
533                 if (strcmp(send.root_path, mount_root) != 0) {
534                         ret = -EINVAL;
535                         fprintf(stderr, "ERROR: all subvols must be from the "
536                                         "same fs.\n");
537                         goto out;
538                 }
539                 free(mount_root);
540
541                 ret = is_subvol_ro(&send, subvol);
542                 if (ret < 0)
543                         goto out;
544                 if (!ret) {
545                         ret = -EINVAL;
546                         fprintf(stderr, "ERROR: %s is not read-only.\n",
547                                         subvol);
548                         goto out;
549                 }
550                 free(subvol);
551         }
552
553         for (i = optind; i < argc; i++) {
554                 subvol = argv[i];
555
556                 fprintf(stderr, "At subvol %s\n", subvol);
557
558                 subvol = realpath(subvol, NULL);
559                 if (!subvol) {
560                         ret = -errno;
561                         fprintf(stderr, "ERROR: realpath %s failed. "
562                                         "%s\n", argv[i], strerror(-ret));
563                         goto out;
564                 }
565
566                 ret = get_root_id(&send, get_subvol_name(&send, subvol),
567                                 &root_id);
568                 if (ret < 0) {
569                         fprintf(stderr, "ERROR: could not resolve root_id "
570                                         "for %s\n", subvol);
571                         goto out;
572                 }
573
574                 if (!parent_root_id) {
575                         ret = find_good_parent(&send, root_id, &parent_root_id);
576                         if (ret < 0)
577                                 parent_root_id = 0;
578                 }
579
580                 ret = is_subvol_ro(&send, subvol);
581                 if (ret < 0)
582                         goto out;
583                 if (!ret) {
584                         ret = -EINVAL;
585                         fprintf(stderr, "ERROR: %s is not read-only.\n",
586                                         subvol);
587                         goto out;
588                 }
589
590                 ret = do_send(&send, root_id, parent_root_id);
591                 if (ret < 0)
592                         goto out;
593
594                 /* done with this subvol, so add it to the clone sources */
595                 add_clone_source(&send, root_id);
596
597                 parent_root_id = 0;
598                 free(subvol);
599         }
600
601         ret = 0;
602
603 out:
604         if (send.mnt_fd >= 0)
605                 close(send.mnt_fd);
606         return ret;
607 }
608
609 static const char * const send_cmd_group_usage[] = {
610         "btrfs send <command> <args>",
611         NULL
612 };
613
614 static const char * const cmd_send_usage[] = {
615         "btrfs send [-v] [-i <subvol>] [-p <parent>] <subvol>",
616         "Send the subvolume to stdout.",
617         "Sends the subvolume specified by <subvol> to stdout.",
618         "By default, this will send the whole subvolume. To do",
619         "an incremental send, one or multiple '-i <clone_source>'",
620         "arguments have to be specified. A 'clone source' is",
621         "a subvolume that is known to exist on the receiving",
622         "side in exactly the same state as on the sending side.\n",
623         "Normally, a good snapshot parent is searched automatically",
624         "in the list of 'clone sources'. To override this, use",
625         "'-p <parent>' to manually specify a snapshot parent.",
626         "A manually specified snapshot parent is also regarded",
627         "as 'clone source'.\n",
628         "-v               Enable verbose debug output. Each",
629         "                 occurrency of this option increases the",
630         "                 verbose level more.",
631         "-i <subvol>      Informs btrfs send that this subvolume,",
632         "                 can be taken as 'clone source'. This can",
633         "                 be used for incremental sends.",
634         "-p <subvol>      Disable automatic snaphot parent",
635         "                 determination and use <subvol> as parent.",
636         "                 This subvolume is also added to the list",
637         "                 of 'clone sources' (see -i).",
638         "-f <outfile>     Output is normally written to stdout.",
639         "                 To write to a file, use this option.",
640         "                 An alternative would be to use pipes.",
641         NULL
642 };
643
644 const struct cmd_group send_cmd_group = {
645         send_cmd_group_usage, NULL, {
646                 { "send", cmd_send_start, cmd_send_usage, NULL, 0 },
647                 { 0, 0, 0, 0, 0 },
648         },
649 };
650
651 int cmd_send(int argc, char **argv)
652 {
653         return cmd_send_start(argc, argv);
654 }