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