btrfs-progs: rework get_fs_info to remove side effects
[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         default:
57                 return "<illegal result value>";
58         }
59 }
60
61 static const char * const replace_cmd_group_usage[] = {
62         "btrfs replace <command> [<args>]",
63         NULL
64 };
65
66 static int is_numerical(const char *str)
67 {
68         if (!(*str >= '0' && *str <= '9'))
69                 return 0;
70         while (*str >= '0' && *str <= '9')
71                 str++;
72         if (*str != '\0')
73                 return 0;
74         return 1;
75 }
76
77 static int dev_replace_cancel_fd = -1;
78 static void dev_replace_sigint_handler(int signal)
79 {
80         int ret;
81         struct btrfs_ioctl_dev_replace_args args = {0};
82
83         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
84         ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
85         if (ret < 0)
86                 perror("Device replace cancel failed");
87 }
88
89 static int dev_replace_handle_sigint(int fd)
90 {
91         struct sigaction sa = {
92                 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
93         };
94
95         dev_replace_cancel_fd = fd;
96         return sigaction(SIGINT, &sa, NULL);
97 }
98
99 static const char *const cmd_start_replace_usage[] = {
100         "btrfs replace start srcdev|devid targetdev [-Bfr] mount_point",
101         "Replace device of a btrfs filesystem.",
102         "On a live filesystem, duplicate the data to the target device which",
103         "is currently stored on the source device. If the source device is not",
104         "available anymore, or if the -r option is set, the data is built",
105         "only using the RAID redundancy mechanisms. After completion of the",
106         "operation, the source device is removed from the filesystem.",
107         "If the srcdev is a numerical value, it is assumed to be the device id",
108         "of the filesystem which is mounted at mount_point, otherwise it is",
109         "the path to the source device. If the source device is disconnected,",
110         "from the system, you have to use the devid parameter format.",
111         "The targetdev needs to be same size or larger than the srcdev.",
112         "",
113         "-r     only read from srcdev if no other zero-defect mirror exists",
114         "       (enable this if your drive has lots of read errors, the access",
115         "       would be very slow)",
116         "-f     force using and overwriting targetdev even if it looks like",
117         "       containing a valid btrfs filesystem. A valid filesystem is",
118         "       assumed if a btrfs superblock is found which contains a",
119         "       correct checksum. Devices which are currently mounted are",
120         "       never allowed to be used as the targetdev",
121         "-B     do not background",
122         NULL
123 };
124
125 static int cmd_start_replace(int argc, char **argv)
126 {
127         struct btrfs_ioctl_dev_replace_args start_args = {0};
128         struct btrfs_ioctl_dev_replace_args status_args = {0};
129         int ret;
130         int i;
131         int c;
132         int fdmnt = -1;
133         int fdsrcdev = -1;
134         int fddstdev = -1;
135         char *path;
136         char *srcdev;
137         char *dstdev;
138         int avoid_reading_from_srcdev = 0;
139         int force_using_targetdev = 0;
140         u64 total_devs = 1;
141         struct btrfs_fs_devices *fs_devices_mnt = NULL;
142         struct stat st;
143         u64 dstdev_block_count;
144         int do_not_background = 0;
145         int mixed = 0;
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);
173
174         if (fdmnt < 0) {
175                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
176                         path, strerror(errno));
177                 goto leave_with_error;
178         }
179
180         /* check for possible errors before backgrounding */
181         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
182         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
183         if (ret) {
184                 fprintf(stderr,
185                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
186                         path, strerror(errno),
187                         replace_dev_result2string(status_args.result));
188                 goto leave_with_error;
189         }
190
191         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
192                 fprintf(stderr,
193                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
194                         path, replace_dev_result2string(status_args.result));
195                 goto leave_with_error;
196         }
197
198         if (status_args.status.replace_state ==
199             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
200                 fprintf(stderr,
201                         "ERROR: btrfs replace on \"%s\" already started!\n",
202                         path);
203                 goto leave_with_error;
204         }
205
206         srcdev = argv[optind];
207         dstdev = argv[optind + 1];
208
209         if (is_numerical(srcdev)) {
210                 struct btrfs_ioctl_fs_info_args fi_args;
211                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
212
213                 if (atoi(srcdev) == 0) {
214                         fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
215                                 srcdev);
216                         goto leave_with_error;
217                 }
218                 start_args.start.srcdevid = (__u64)atoi(srcdev);
219
220                 ret = get_fs_info(path, &fi_args, &di_args);
221                 if (ret) {
222                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
223                                         "%s\n", strerror(-ret));
224                         free(di_args);
225                         goto leave_with_error;
226                 }
227                 if (!fi_args.num_devices) {
228                         fprintf(stderr, "ERROR: no devices found\n");
229                         free(di_args);
230                         goto leave_with_error;
231                 }
232
233                 for (i = 0; i < fi_args.num_devices; i++)
234                         if (start_args.start.srcdevid == di_args[i].devid)
235                                 break;
236                 free(di_args);
237                 if (i == fi_args.num_devices) {
238                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
239                                 srcdev, path);
240                         goto leave_with_error;
241                 }
242         } else {
243                 fdsrcdev = open(srcdev, O_RDWR);
244                 if (fdsrcdev < 0) {
245                         fprintf(stderr, "Error: Unable to open device '%s'\n",
246                                 srcdev);
247                         goto leave_with_error;
248                 }
249                 ret = fstat(fdsrcdev, &st);
250                 if (ret) {
251                         fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
252                         goto leave_with_error;
253                 }
254                 if (!S_ISBLK(st.st_mode)) {
255                         fprintf(stderr, "Error: '%s' is not a block device\n",
256                                 srcdev);
257                         goto leave_with_error;
258                 }
259                 strncpy((char *)start_args.start.srcdev_name, srcdev,
260                         BTRFS_DEVICE_PATH_NAME_MAX);
261                 close(fdsrcdev);
262                 fdsrcdev = -1;
263                 start_args.start.srcdevid = 0;
264         }
265
266         ret = check_mounted(dstdev);
267         if (ret < 0) {
268                 fprintf(stderr, "Error checking %s mount status\n", dstdev);
269                 goto leave_with_error;
270         }
271         if (ret == 1) {
272                 fprintf(stderr,
273                         "Error, target device %s is in use and currently mounted!\n",
274                         dstdev);
275                 goto leave_with_error;
276         }
277         fddstdev = open(dstdev, O_RDWR);
278         if (fddstdev < 0) {
279                 fprintf(stderr, "Unable to open %s\n", dstdev);
280                 goto leave_with_error;
281         }
282         ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
283                                     &total_devs, BTRFS_SUPER_INFO_OFFSET);
284         if (ret >= 0 && !force_using_targetdev) {
285                 fprintf(stderr,
286                         "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
287                         dstdev);
288                 goto leave_with_error;
289         }
290         ret = fstat(fddstdev, &st);
291         if (ret) {
292                 fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
293                 goto leave_with_error;
294         }
295         if (!S_ISBLK(st.st_mode)) {
296                 fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
297                 goto leave_with_error;
298         }
299         strncpy((char *)start_args.start.tgtdev_name, dstdev,
300                 BTRFS_DEVICE_PATH_NAME_MAX);
301         if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
302                                  &mixed, 0)) {
303                 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
304                         dstdev);
305                 goto leave_with_error;
306         }
307         close(fddstdev);
308         fddstdev = -1;
309
310         dev_replace_handle_sigint(fdmnt);
311         if (!do_not_background) {
312                 if (daemon(0, 0) < 0) {
313                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
314                                 strerror(errno));
315                         goto leave_with_error;
316                 }
317         }
318
319         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
320         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
321         if (do_not_background) {
322                 if (ret) {
323                         fprintf(stderr,
324                                 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
325                                 path, strerror(errno),
326                                 replace_dev_result2string(start_args.result));
327                         goto leave_with_error;
328                 }
329
330                 if (start_args.result !=
331                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
332                         fprintf(stderr,
333                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
334                                 path,
335                                 replace_dev_result2string(start_args.result));
336                         goto leave_with_error;
337                 }
338         }
339         close(fdmnt);
340         return 0;
341
342 leave_with_error:
343         if (fdmnt != -1)
344                 close(fdmnt);
345         if (fdsrcdev != -1)
346                 close(fdsrcdev);
347         if (fddstdev != -1)
348                 close(fddstdev);
349         return -1;
350 }
351
352 static const char *const cmd_status_replace_usage[] = {
353         "btrfs replace status mount_point [-1]",
354         "Print status and progress information of a running device replace",
355         "operation",
356         "",
357         "-1     print once instead of print continously until the replace",
358         "       operation finishes (or is canceled)",
359         NULL
360 };
361
362 static int cmd_status_replace(int argc, char **argv)
363 {
364         int fd;
365         int e;
366         int c;
367         char *path;
368         int once = 0;
369         int ret;
370
371         while ((c = getopt(argc, argv, "1")) != -1) {
372                 switch (c) {
373                 case '1':
374                         once = 1;
375                         break;
376                 case '?':
377                 default:
378                         usage(cmd_status_replace_usage);
379                 }
380         }
381
382         if (check_argc_exact(argc - optind, 1))
383                 usage(cmd_status_replace_usage);
384
385         path = argv[optind];
386         fd = open_file_or_dir(path);
387         e = errno;
388         if (fd < 0) {
389                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
390                         path, strerror(e));
391                 return -1;
392         }
393
394         ret = print_replace_status(fd, path, once);
395         close(fd);
396         return ret;
397 }
398
399 static int print_replace_status(int fd, const char *path, int once)
400 {
401         struct btrfs_ioctl_dev_replace_args args = {0};
402         struct btrfs_ioctl_dev_replace_status_params *status;
403         int ret;
404         int prevent_loop = 0;
405         int skip_stats;
406         int num_chars;
407         char string1[80];
408         char string2[80];
409         char string3[80];
410
411         for (;;) {
412                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
413                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
414                 if (ret) {
415                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
416                                 path, strerror(errno),
417                                 replace_dev_result2string(args.result));
418                         return ret;
419                 }
420
421                 status = &args.status;
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                 skip_stats = 0;
430                 num_chars = 0;
431                 switch (status->replace_state) {
432                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
433                         num_chars =
434                                 printf("%s done",
435                                        progress2string(string3,
436                                                        sizeof(string3),
437                                                        status->progress_1000));
438                         break;
439                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
440                         prevent_loop = 1;
441                         printf("Started on %s, finished on %s",
442                                time2string(string1, sizeof(string1),
443                                            status->time_started),
444                                time2string(string2, sizeof(string2),
445                                            status->time_stopped));
446                         break;
447                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
448                         prevent_loop = 1;
449                         printf("Started on %s, canceled on %s at %s",
450                                time2string(string1, sizeof(string1),
451                                            status->time_started),
452                                time2string(string2, sizeof(string2),
453                                            status->time_stopped),
454                                progress2string(string3, sizeof(string3),
455                                                status->progress_1000));
456                         break;
457                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
458                         prevent_loop = 1;
459                         printf("Started on %s, suspended 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_NEVER_STARTED:
468                         prevent_loop = 1;
469                         skip_stats = 1;
470                         printf("Never started");
471                         break;
472                 default:
473                         prevent_loop = 1;
474                         assert(0);
475                         break;
476                 }
477
478                 if (!skip_stats)
479                         num_chars += printf(
480                                 ", %llu write errs, %llu uncorr. read errs",
481                                 (unsigned long long)status->num_write_errors,
482                                 (unsigned long long)
483                                  status->num_uncorrectable_read_errors);
484                 if (once || prevent_loop) {
485                         printf("\n");
486                         return 0;
487                 }
488
489                 fflush(stdout);
490                 sleep(1);
491                 while (num_chars > 0) {
492                         putchar('\b');
493                         num_chars--;
494                 }
495         }
496
497         return 0;
498 }
499
500 static char *
501 time2string(char *buf, size_t s, __u64 t)
502 {
503         struct tm t_tm;
504         time_t t_time_t;
505
506         t_time_t = (time_t)t;
507         assert((__u64)t_time_t == t);
508         localtime_r(&t_time_t, &t_tm);
509         strftime(buf, s, "%e.%b %T", &t_tm);
510         return buf;
511 }
512
513 static char *
514 progress2string(char *buf, size_t s, int progress_1000)
515 {
516         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
517         assert(s > 0);
518         buf[s - 1] = '\0';
519         return buf;
520 }
521
522 static const char *const cmd_cancel_replace_usage[] = {
523         "btrfs replace cancel mount_point",
524         "Cancel a running device replace operation.",
525         NULL
526 };
527
528 static int cmd_cancel_replace(int argc, char **argv)
529 {
530         struct btrfs_ioctl_dev_replace_args args = {0};
531         int ret;
532         int c;
533         int fd;
534         int e;
535         char *path;
536
537         while ((c = getopt(argc, argv, "")) != -1) {
538                 switch (c) {
539                 case '?':
540                 default:
541                         usage(cmd_cancel_replace_usage);
542                 }
543         }
544
545         if (check_argc_exact(argc - optind, 1))
546                 usage(cmd_cancel_replace_usage);
547
548         path = argv[optind];
549         fd = open_file_or_dir(path);
550         if (fd < 0) {
551                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
552                         path, strerror(errno));
553                 return -1;
554         }
555
556         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
557         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
558         e = errno;
559         close(fd);
560         if (ret) {
561                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
562                         path, strerror(e),
563                         replace_dev_result2string(args.result));
564                 return ret;
565         }
566
567         return 0;
568 }
569
570 const struct cmd_group replace_cmd_group = {
571         replace_cmd_group_usage, NULL, {
572                 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
573                   0 },
574                 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
575                   0 },
576                 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
577                   0 },
578                 { 0, 0, 0, 0, 0 }
579         }
580 };
581
582 int cmd_replace(int argc, char **argv)
583 {
584         return handle_command_group(&replace_cmd_group, argc, argv);
585 }