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