btrfs-progs: move is_numerical() helper to utils and rename
[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         int mixed = 0;
133         DIR *dirstream = NULL;
134         u64 srcdev_size;
135         u64 dstdev_size;
136
137         while ((c = getopt(argc, argv, "Brf")) != -1) {
138                 switch (c) {
139                 case 'B':
140                         do_not_background = 1;
141                         break;
142                 case 'r':
143                         avoid_reading_from_srcdev = 1;
144                         break;
145                 case 'f':
146                         force_using_targetdev = 1;
147                         break;
148                 case '?':
149                 default:
150                         usage(cmd_replace_start_usage);
151                 }
152         }
153
154         start_args.start.cont_reading_from_srcdev_mode =
155                 avoid_reading_from_srcdev ?
156                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
157                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
158         if (check_argc_exact(argc - optind, 3))
159                 usage(cmd_replace_start_usage);
160         path = argv[optind + 2];
161
162         fdmnt = open_path_or_dev_mnt(path, &dirstream, 1);
163         if (fdmnt < 0)
164                 goto leave_with_error;
165
166         /* check for possible errors before backgrounding */
167         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
168         status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
169         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
170         if (ret) {
171                 fprintf(stderr,
172                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
173                         path, strerror(errno));
174                 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
175                         fprintf(stderr, ", %s\n",
176                                 replace_dev_result2string(status_args.result));
177                 else
178                         fprintf(stderr, "\n");
179                 goto leave_with_error;
180         }
181
182         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
183                 fprintf(stderr,
184                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
185                         path, replace_dev_result2string(status_args.result));
186                 goto leave_with_error;
187         }
188
189         if (status_args.status.replace_state ==
190             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
191                 fprintf(stderr,
192                         "ERROR: btrfs replace on \"%s\" already started!\n",
193                         path);
194                 goto leave_with_error;
195         }
196
197         srcdev = argv[optind];
198         dstdev = canonicalize_path(argv[optind + 1]);
199         if (!dstdev) {
200                 fprintf(stderr,
201                         "ERROR: Could not canonicalize path '%s': %s\n",
202                         argv[optind + 1], strerror(errno));
203                 goto leave_with_error;
204         }
205
206         if (string_is_numerical(srcdev)) {
207                 struct btrfs_ioctl_fs_info_args fi_args;
208                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
209
210                 start_args.start.srcdevid = arg_strtou64(srcdev);
211
212                 ret = get_fs_info(path, &fi_args, &di_args);
213                 if (ret) {
214                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
215                                         "%s\n", strerror(-ret));
216                         free(di_args);
217                         goto leave_with_error;
218                 }
219                 if (!fi_args.num_devices) {
220                         fprintf(stderr, "ERROR: no devices found\n");
221                         free(di_args);
222                         goto leave_with_error;
223                 }
224
225                 for (i = 0; i < fi_args.num_devices; i++)
226                         if (start_args.start.srcdevid == di_args[i].devid)
227                                 break;
228                 srcdev_size = di_args[i].total_bytes;
229                 free(di_args);
230                 if (i == fi_args.num_devices) {
231                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
232                                 srcdev, path);
233                         goto leave_with_error;
234                 }
235         } else if (is_block_device(srcdev) > 0) {
236                 strncpy((char *)start_args.start.srcdev_name, srcdev,
237                         BTRFS_DEVICE_PATH_NAME_MAX);
238                 start_args.start.srcdevid = 0;
239                 srcdev_size = get_partition_size(srcdev);
240         } else {
241                 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
242                 goto leave_with_error;
243         }
244
245         ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
246         if (ret)
247                 goto leave_with_error;
248
249         dstdev_size = get_partition_size(dstdev);
250         if (srcdev_size > dstdev_size) {
251                 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
252                         srcdev_size);
253                 goto leave_with_error;
254         }
255
256         fddstdev = open(dstdev, O_RDWR);
257         if (fddstdev < 0) {
258                 fprintf(stderr, "Unable to open %s\n", dstdev);
259                 goto leave_with_error;
260         }
261         strncpy((char *)start_args.start.tgtdev_name, dstdev,
262                 BTRFS_DEVICE_PATH_NAME_MAX);
263         ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
264                                  &mixed, 0);
265         if (ret)
266                 goto leave_with_error;
267
268         close(fddstdev);
269         fddstdev = -1;
270         free(dstdev);
271         dstdev = NULL;
272
273         dev_replace_handle_sigint(fdmnt);
274         if (!do_not_background) {
275                 if (daemon(0, 0) < 0) {
276                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
277                                 strerror(errno));
278                         goto leave_with_error;
279                 }
280         }
281
282         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
283         start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
284         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
285         if (do_not_background) {
286                 if (ret) {
287                         fprintf(stderr,
288                                 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
289                                 path, strerror(errno));
290                         if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
291                                 fprintf(stderr, ", %s\n",
292                                         replace_dev_result2string(start_args.result));
293                         else
294                                 fprintf(stderr, "\n");
295
296                         if (errno == EOPNOTSUPP)
297                                 fprintf(stderr,
298                                         "WARNING: dev_replace does not yet handle RAID5/6\n");
299
300                         goto leave_with_error;
301                 }
302
303                 if (start_args.result !=
304                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
305                         fprintf(stderr,
306                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
307                                 path,
308                                 replace_dev_result2string(start_args.result));
309                         goto leave_with_error;
310                 }
311         }
312         close_file_or_dir(fdmnt, dirstream);
313         btrfs_close_all_devices();
314         return 0;
315
316 leave_with_error:
317         if (dstdev)
318                 free(dstdev);
319         if (fdmnt != -1)
320                 close(fdmnt);
321         if (fddstdev != -1)
322                 close(fddstdev);
323         btrfs_close_all_devices();
324         return 1;
325 }
326
327 static const char *const cmd_replace_status_usage[] = {
328         "btrfs replace status [-1] <mount_point>",
329         "Print status and progress information of a running device replace",
330         "operation",
331         "",
332         "-1     print once instead of print continuously until the replace",
333         "       operation finishes (or is canceled)",
334         NULL
335 };
336
337 static int cmd_replace_status(int argc, char **argv)
338 {
339         int fd;
340         int c;
341         char *path;
342         int once = 0;
343         int ret;
344         DIR *dirstream = NULL;
345
346         while ((c = getopt(argc, argv, "1")) != -1) {
347                 switch (c) {
348                 case '1':
349                         once = 1;
350                         break;
351                 case '?':
352                 default:
353                         usage(cmd_replace_status_usage);
354                 }
355         }
356
357         if (check_argc_exact(argc - optind, 1))
358                 usage(cmd_replace_status_usage);
359
360         path = argv[optind];
361         fd = btrfs_open_dir(path, &dirstream, 1);
362         if (fd < 0)
363                 return 1;
364
365         ret = print_replace_status(fd, path, once);
366         close_file_or_dir(fd, dirstream);
367         return !!ret;
368 }
369
370 static int print_replace_status(int fd, const char *path, int once)
371 {
372         struct btrfs_ioctl_dev_replace_args args = {0};
373         struct btrfs_ioctl_dev_replace_status_params *status;
374         int ret;
375         int prevent_loop = 0;
376         int skip_stats;
377         int num_chars;
378         char string1[80];
379         char string2[80];
380         char string3[80];
381
382         for (;;) {
383                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
384                 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
385                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
386                 if (ret) {
387                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
388                                 path, strerror(errno));
389                         if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
390                                 fprintf(stderr, ", %s\n",
391                                         replace_dev_result2string(args.result));
392                         else
393                                 fprintf(stderr, "\n");
394                         return ret;
395                 }
396
397                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
398                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
399                                 path,
400                                 replace_dev_result2string(args.result));
401                         return -1;
402                 }
403
404                 status = &args.status;
405
406                 skip_stats = 0;
407                 num_chars = 0;
408                 switch (status->replace_state) {
409                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
410                         num_chars =
411                                 printf("%s done",
412                                        progress2string(string3,
413                                                        sizeof(string3),
414                                                        status->progress_1000));
415                         break;
416                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
417                         prevent_loop = 1;
418                         printf("Started on %s, finished on %s",
419                                time2string(string1, sizeof(string1),
420                                            status->time_started),
421                                time2string(string2, sizeof(string2),
422                                            status->time_stopped));
423                         break;
424                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
425                         prevent_loop = 1;
426                         printf("Started on %s, canceled on %s at %s",
427                                time2string(string1, sizeof(string1),
428                                            status->time_started),
429                                time2string(string2, sizeof(string2),
430                                            status->time_stopped),
431                                progress2string(string3, sizeof(string3),
432                                                status->progress_1000));
433                         break;
434                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
435                         prevent_loop = 1;
436                         printf("Started on %s, suspended on %s at %s",
437                                time2string(string1, sizeof(string1),
438                                            status->time_started),
439                                time2string(string2, sizeof(string2),
440                                            status->time_stopped),
441                                progress2string(string3, sizeof(string3),
442                                                status->progress_1000));
443                         break;
444                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
445                         prevent_loop = 1;
446                         skip_stats = 1;
447                         printf("Never started");
448                         break;
449                 default:
450                         fprintf(stderr,
451         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
452                                         path, status->replace_state);
453                         return -EINVAL;
454                 }
455
456                 if (!skip_stats)
457                         num_chars += printf(
458                                 ", %llu write errs, %llu uncorr. read errs",
459                                 (unsigned long long)status->num_write_errors,
460                                 (unsigned long long)
461                                  status->num_uncorrectable_read_errors);
462                 if (once || prevent_loop) {
463                         printf("\n");
464                         break;
465                 }
466
467                 fflush(stdout);
468                 sleep(1);
469                 while (num_chars > 0) {
470                         putchar('\b');
471                         num_chars--;
472                 }
473         }
474
475         return 0;
476 }
477
478 static char *
479 time2string(char *buf, size_t s, __u64 t)
480 {
481         struct tm t_tm;
482         time_t t_time_t;
483
484         t_time_t = (time_t)t;
485         assert((__u64)t_time_t == t);
486         localtime_r(&t_time_t, &t_tm);
487         strftime(buf, s, "%e.%b %T", &t_tm);
488         return buf;
489 }
490
491 static char *
492 progress2string(char *buf, size_t s, int progress_1000)
493 {
494         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
495         assert(s > 0);
496         buf[s - 1] = '\0';
497         return buf;
498 }
499
500 static const char *const cmd_replace_cancel_usage[] = {
501         "btrfs replace cancel <mount_point>",
502         "Cancel a running device replace operation.",
503         NULL
504 };
505
506 static int cmd_replace_cancel(int argc, char **argv)
507 {
508         struct btrfs_ioctl_dev_replace_args args = {0};
509         int ret;
510         int c;
511         int fd;
512         int e;
513         char *path;
514         DIR *dirstream = NULL;
515
516         while ((c = getopt(argc, argv, "")) != -1) {
517                 switch (c) {
518                 case '?':
519                 default:
520                         usage(cmd_replace_cancel_usage);
521                 }
522         }
523
524         if (check_argc_exact(argc - optind, 1))
525                 usage(cmd_replace_cancel_usage);
526
527         path = argv[optind];
528         fd = btrfs_open_dir(path, &dirstream, 1);
529         if (fd < 0)
530                 return 1;
531
532         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
533         args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
534         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
535         e = errno;
536         close_file_or_dir(fd, dirstream);
537         if (ret) {
538                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
539                         path, strerror(e));
540                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
541                         fprintf(stderr, ", %s\n",
542                                 replace_dev_result2string(args.result));
543                 else
544                         fprintf(stderr, "\n");
545                 return 1;
546         }
547         if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
548                 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
549                         path, replace_dev_result2string(args.result));
550                 return 2;
551         }
552         return 0;
553 }
554
555 static const char replace_cmd_group_info[] =
556 "replace a device in the filesystem";
557
558 const struct cmd_group replace_cmd_group = {
559         replace_cmd_group_usage, replace_cmd_group_info, {
560                 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
561                   0 },
562                 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
563                   0 },
564                 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
565                   0 },
566                 NULL_CMD_STRUCT
567         }
568 };
569
570 int cmd_replace(int argc, char **argv)
571 {
572         return handle_command_group(&replace_cmd_group, argc, argv);
573 }