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