btrfs-progs: Remove all btrfs_close_all_devices in sub-command
[platform/upstream/btrfs-progs.git] / cmds-replace.c
1 /*
2  * Copyright (C) 2012 STRATO.  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 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/ioctl.h>
25 #include <errno.h>
26 #include <sys/stat.h>
27 #include <time.h>
28 #include <assert.h>
29 #include <inttypes.h>
30 #include <sys/wait.h>
31
32 #include "kerncompat.h"
33 #include "ctree.h"
34 #include "ioctl.h"
35 #include "utils.h"
36 #include "volumes.h"
37 #include "disk-io.h"
38
39 #include "commands.h"
40
41
42 static int print_replace_status(int fd, const char *path, int once);
43 static char *time2string(char *buf, size_t s, __u64 t);
44 static char *progress2string(char *buf, size_t s, int progress_1000);
45
46
47 static const char *replace_dev_result2string(__u64 result)
48 {
49         switch (result) {
50         case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
51                 return "no error";
52         case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
53                 return "not started";
54         case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
55                 return "already started";
56         case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
57                 return "scrub is in progress";
58         default:
59                 return "<illegal result value>";
60         }
61 }
62
63 static const char * const replace_cmd_group_usage[] = {
64         "btrfs replace <command> [<args>]",
65         NULL
66 };
67
68 static int dev_replace_cancel_fd = -1;
69 static void dev_replace_sigint_handler(int signal)
70 {
71         int ret;
72         struct btrfs_ioctl_dev_replace_args args = {0};
73
74         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
75         ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
76         if (ret < 0)
77                 perror("Device replace cancel failed");
78 }
79
80 static int dev_replace_handle_sigint(int fd)
81 {
82         struct sigaction sa = {
83                 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
84         };
85
86         dev_replace_cancel_fd = fd;
87         return sigaction(SIGINT, &sa, NULL);
88 }
89
90 static const char *const cmd_replace_start_usage[] = {
91         "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
92         "Replace device of a btrfs filesystem.",
93         "On a live filesystem, duplicate the data to the target device which",
94         "is currently stored on the source device. If the source device is not",
95         "available anymore, or if the -r option is set, the data is built",
96         "only using the RAID redundancy mechanisms. After completion of the",
97         "operation, the source device is removed from the filesystem.",
98         "If the <srcdev> is a numerical value, it is assumed to be the device id",
99         "of the filesystem which is mounted at <mount_point>, otherwise it is",
100         "the path to the source device. If the source device is disconnected,",
101         "from the system, you have to use the <devid> parameter format.",
102         "The <targetdev> needs to be same size or larger than the <srcdev>.",
103         "",
104         "-r     only read from <srcdev> if no other zero-defect mirror exists",
105         "       (enable this if your drive has lots of read errors, the access",
106         "       would be very slow)",
107         "-f     force using and overwriting <targetdev> even if it looks like",
108         "       containing a valid btrfs filesystem. A valid filesystem is",
109         "       assumed if a btrfs superblock is found which contains a",
110         "       correct checksum. Devices which are currently mounted are",
111         "       never allowed to be used as the <targetdev>",
112         "-B     do not background",
113         NULL
114 };
115
116 static int cmd_replace_start(int argc, char **argv)
117 {
118         struct btrfs_ioctl_dev_replace_args start_args = {0};
119         struct btrfs_ioctl_dev_replace_args status_args = {0};
120         int ret;
121         int i;
122         int c;
123         int fdmnt = -1;
124         int fddstdev = -1;
125         char *path;
126         char *srcdev;
127         char *dstdev = NULL;
128         int avoid_reading_from_srcdev = 0;
129         int force_using_targetdev = 0;
130         u64 dstdev_block_count;
131         int do_not_background = 0;
132         DIR *dirstream = NULL;
133         u64 srcdev_size;
134         u64 dstdev_size;
135
136         while ((c = getopt(argc, argv, "Brf")) != -1) {
137                 switch (c) {
138                 case 'B':
139                         do_not_background = 1;
140                         break;
141                 case 'r':
142                         avoid_reading_from_srcdev = 1;
143                         break;
144                 case 'f':
145                         force_using_targetdev = 1;
146                         break;
147                 case '?':
148                 default:
149                         usage(cmd_replace_start_usage);
150                 }
151         }
152
153         start_args.start.cont_reading_from_srcdev_mode =
154                 avoid_reading_from_srcdev ?
155                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
156                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
157         if (check_argc_exact(argc - optind, 3))
158                 usage(cmd_replace_start_usage);
159         path = argv[optind + 2];
160
161         fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
162         if (fdmnt < 0)
163                 goto leave_with_error;
164
165         /* check for possible errors before backgrounding */
166         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
167         status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
168         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
169         if (ret) {
170                 fprintf(stderr,
171                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
172                         path, strerror(errno));
173                 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
174                         fprintf(stderr, ", %s\n",
175                                 replace_dev_result2string(status_args.result));
176                 else
177                         fprintf(stderr, "\n");
178                 goto leave_with_error;
179         }
180
181         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
182                 fprintf(stderr,
183                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
184                         path, replace_dev_result2string(status_args.result));
185                 goto leave_with_error;
186         }
187
188         if (status_args.status.replace_state ==
189             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
190                 fprintf(stderr,
191                         "ERROR: btrfs replace on \"%s\" already started!\n",
192                         path);
193                 goto leave_with_error;
194         }
195
196         srcdev = argv[optind];
197         dstdev = canonicalize_path(argv[optind + 1]);
198         if (!dstdev) {
199                 fprintf(stderr,
200                         "ERROR: Could not canonicalize path '%s': %s\n",
201                         argv[optind + 1], strerror(errno));
202                 goto leave_with_error;
203         }
204
205         if (string_is_numerical(srcdev)) {
206                 struct btrfs_ioctl_fs_info_args fi_args;
207                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
208
209                 start_args.start.srcdevid = arg_strtou64(srcdev);
210
211                 ret = get_fs_info(path, &fi_args, &di_args);
212                 if (ret) {
213                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
214                                         "%s\n", strerror(-ret));
215                         free(di_args);
216                         goto leave_with_error;
217                 }
218                 if (!fi_args.num_devices) {
219                         fprintf(stderr, "ERROR: no devices found\n");
220                         free(di_args);
221                         goto leave_with_error;
222                 }
223
224                 for (i = 0; i < fi_args.num_devices; i++)
225                         if (start_args.start.srcdevid == di_args[i].devid)
226                                 break;
227                 srcdev_size = di_args[i].total_bytes;
228                 free(di_args);
229                 if (i == fi_args.num_devices) {
230                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
231                                 srcdev, path);
232                         goto leave_with_error;
233                 }
234         } else if (is_block_device(srcdev) > 0) {
235                 strncpy((char *)start_args.start.srcdev_name, srcdev,
236                         BTRFS_DEVICE_PATH_NAME_MAX);
237                 start_args.start.srcdevid = 0;
238                 srcdev_size = get_partition_size(srcdev);
239         } else {
240                 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
241                 goto leave_with_error;
242         }
243
244         ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
245         if (ret)
246                 goto leave_with_error;
247
248         dstdev_size = get_partition_size(dstdev);
249         if (srcdev_size > dstdev_size) {
250                 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
251                         srcdev_size);
252                 goto leave_with_error;
253         }
254
255         fddstdev = open(dstdev, O_RDWR);
256         if (fddstdev < 0) {
257                 fprintf(stderr, "Unable to open %s\n", dstdev);
258                 goto leave_with_error;
259         }
260         strncpy((char *)start_args.start.tgtdev_name, dstdev,
261                 BTRFS_DEVICE_PATH_NAME_MAX);
262         ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
263                                 0);
264         if (ret)
265                 goto leave_with_error;
266
267         close(fddstdev);
268         fddstdev = -1;
269         free(dstdev);
270         dstdev = NULL;
271
272         dev_replace_handle_sigint(fdmnt);
273         if (!do_not_background) {
274                 if (daemon(0, 0) < 0) {
275                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
276                                 strerror(errno));
277                         goto leave_with_error;
278                 }
279         }
280
281         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
282         start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
283         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
284         if (do_not_background) {
285                 if (ret) {
286                         fprintf(stderr,
287                                 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
288                                 path, strerror(errno));
289                         if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
290                                 fprintf(stderr, ", %s\n",
291                                         replace_dev_result2string(start_args.result));
292                         else
293                                 fprintf(stderr, "\n");
294
295                         if (errno == EOPNOTSUPP)
296                                 fprintf(stderr,
297                                         "WARNING: dev_replace does not yet handle RAID5/6\n");
298
299                         goto leave_with_error;
300                 }
301
302                 if (start_args.result !=
303                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
304                         fprintf(stderr,
305                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
306                                 path,
307                                 replace_dev_result2string(start_args.result));
308                         goto leave_with_error;
309                 }
310         }
311         close_file_or_dir(fdmnt, dirstream);
312         return 0;
313
314 leave_with_error:
315         if (dstdev)
316                 free(dstdev);
317         if (fdmnt != -1)
318                 close(fdmnt);
319         if (fddstdev != -1)
320                 close(fddstdev);
321         return 1;
322 }
323
324 static const char *const cmd_replace_status_usage[] = {
325         "btrfs replace status [-1] <mount_point>",
326         "Print status and progress information of a running device replace",
327         "operation",
328         "",
329         "-1     print once instead of print continuously until the replace",
330         "       operation finishes (or is canceled)",
331         NULL
332 };
333
334 static int cmd_replace_status(int argc, char **argv)
335 {
336         int fd;
337         int c;
338         char *path;
339         int once = 0;
340         int ret;
341         DIR *dirstream = NULL;
342
343         while ((c = getopt(argc, argv, "1")) != -1) {
344                 switch (c) {
345                 case '1':
346                         once = 1;
347                         break;
348                 case '?':
349                 default:
350                         usage(cmd_replace_status_usage);
351                 }
352         }
353
354         if (check_argc_exact(argc - optind, 1))
355                 usage(cmd_replace_status_usage);
356
357         path = argv[optind];
358         fd = btrfs_open_dir(path, &dirstream, 1);
359         if (fd < 0)
360                 return 1;
361
362         ret = print_replace_status(fd, path, once);
363         close_file_or_dir(fd, dirstream);
364         return !!ret;
365 }
366
367 static int print_replace_status(int fd, const char *path, int once)
368 {
369         struct btrfs_ioctl_dev_replace_args args = {0};
370         struct btrfs_ioctl_dev_replace_status_params *status;
371         int ret;
372         int prevent_loop = 0;
373         int skip_stats;
374         int num_chars;
375         char string1[80];
376         char string2[80];
377         char string3[80];
378
379         for (;;) {
380                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
381                 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
382                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
383                 if (ret) {
384                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
385                                 path, strerror(errno));
386                         if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
387                                 fprintf(stderr, ", %s\n",
388                                         replace_dev_result2string(args.result));
389                         else
390                                 fprintf(stderr, "\n");
391                         return ret;
392                 }
393
394                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
395                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
396                                 path,
397                                 replace_dev_result2string(args.result));
398                         return -1;
399                 }
400
401                 status = &args.status;
402
403                 skip_stats = 0;
404                 num_chars = 0;
405                 switch (status->replace_state) {
406                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
407                         num_chars =
408                                 printf("%s done",
409                                        progress2string(string3,
410                                                        sizeof(string3),
411                                                        status->progress_1000));
412                         break;
413                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
414                         prevent_loop = 1;
415                         printf("Started on %s, finished on %s",
416                                time2string(string1, sizeof(string1),
417                                            status->time_started),
418                                time2string(string2, sizeof(string2),
419                                            status->time_stopped));
420                         break;
421                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
422                         prevent_loop = 1;
423                         printf("Started on %s, canceled on %s at %s",
424                                time2string(string1, sizeof(string1),
425                                            status->time_started),
426                                time2string(string2, sizeof(string2),
427                                            status->time_stopped),
428                                progress2string(string3, sizeof(string3),
429                                                status->progress_1000));
430                         break;
431                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
432                         prevent_loop = 1;
433                         printf("Started on %s, suspended on %s at %s",
434                                time2string(string1, sizeof(string1),
435                                            status->time_started),
436                                time2string(string2, sizeof(string2),
437                                            status->time_stopped),
438                                progress2string(string3, sizeof(string3),
439                                                status->progress_1000));
440                         break;
441                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
442                         prevent_loop = 1;
443                         skip_stats = 1;
444                         printf("Never started");
445                         break;
446                 default:
447                         fprintf(stderr,
448         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
449                                         path, status->replace_state);
450                         return -EINVAL;
451                 }
452
453                 if (!skip_stats)
454                         num_chars += printf(
455                                 ", %llu write errs, %llu uncorr. read errs",
456                                 (unsigned long long)status->num_write_errors,
457                                 (unsigned long long)
458                                  status->num_uncorrectable_read_errors);
459                 if (once || prevent_loop) {
460                         printf("\n");
461                         break;
462                 }
463
464                 fflush(stdout);
465                 sleep(1);
466                 while (num_chars > 0) {
467                         putchar('\b');
468                         num_chars--;
469                 }
470         }
471
472         return 0;
473 }
474
475 static char *
476 time2string(char *buf, size_t s, __u64 t)
477 {
478         struct tm t_tm;
479         time_t t_time_t;
480
481         t_time_t = (time_t)t;
482         assert((__u64)t_time_t == t);
483         localtime_r(&t_time_t, &t_tm);
484         strftime(buf, s, "%e.%b %T", &t_tm);
485         return buf;
486 }
487
488 static char *
489 progress2string(char *buf, size_t s, int progress_1000)
490 {
491         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
492         assert(s > 0);
493         buf[s - 1] = '\0';
494         return buf;
495 }
496
497 static const char *const cmd_replace_cancel_usage[] = {
498         "btrfs replace cancel <mount_point>",
499         "Cancel a running device replace operation.",
500         NULL
501 };
502
503 static int cmd_replace_cancel(int argc, char **argv)
504 {
505         struct btrfs_ioctl_dev_replace_args args = {0};
506         int ret;
507         int c;
508         int fd;
509         int e;
510         char *path;
511         DIR *dirstream = NULL;
512
513         while ((c = getopt(argc, argv, "")) != -1) {
514                 switch (c) {
515                 case '?':
516                 default:
517                         usage(cmd_replace_cancel_usage);
518                 }
519         }
520
521         if (check_argc_exact(argc - optind, 1))
522                 usage(cmd_replace_cancel_usage);
523
524         path = argv[optind];
525         fd = btrfs_open_dir(path, &dirstream, 1);
526         if (fd < 0)
527                 return 1;
528
529         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
530         args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
531         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
532         e = errno;
533         close_file_or_dir(fd, dirstream);
534         if (ret) {
535                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
536                         path, strerror(e));
537                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
538                         fprintf(stderr, ", %s\n",
539                                 replace_dev_result2string(args.result));
540                 else
541                         fprintf(stderr, "\n");
542                 return 1;
543         }
544         if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
545                 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
546                         path, replace_dev_result2string(args.result));
547                 return 2;
548         }
549         return 0;
550 }
551
552 static const char replace_cmd_group_info[] =
553 "replace a device in the filesystem";
554
555 const struct cmd_group replace_cmd_group = {
556         replace_cmd_group_usage, replace_cmd_group_info, {
557                 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
558                   0 },
559                 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
560                   0 },
561                 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
562                   0 },
563                 NULL_CMD_STRUCT
564         }
565 };
566
567 int cmd_replace(int argc, char **argv)
568 {
569         return handle_command_group(&replace_cmd_group, argc, argv);
570 }