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