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