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