]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/partition/growfs.c
Add mkfs wrapper which first checks if the partition is empty
[thirdparty/systemd.git] / src / partition / growfs.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2017 Zbigniew Jędrzejewski-Szmek
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <getopt.h>
24 #include <linux/magic.h>
25 #include <sys/ioctl.h>
26 #include <sys/mount.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/vfs.h>
30
31 #include "crypt-util.h"
32 #include "device-nodes.h"
33 #include "dissect-image.h"
34 #include "escape.h"
35 #include "fd-util.h"
36 #include "format-util.h"
37 #include "log.h"
38 #include "missing.h"
39 #include "mount-util.h"
40 #include "parse-util.h"
41 #include "path-util.h"
42 #include "strv.h"
43
44 const char *arg_target = NULL;
45 bool arg_dry_run = false;
46
47 static int resize_ext4(const char *path, int mountfd, int devfd, uint64_t numblocks, uint64_t blocksize) {
48 assert((uint64_t) (int) blocksize == blocksize);
49
50 if (arg_dry_run)
51 return 0;
52
53 if (ioctl(mountfd, EXT4_IOC_RESIZE_FS, &numblocks) != 0)
54 return log_error_errno(errno, "Failed to resize \"%s\" to %"PRIu64" blocks (ext4): %m",
55 path, numblocks);
56
57 return 0;
58 }
59
60 static int resize_btrfs(const char *path, int mountfd, int devfd, uint64_t numblocks, uint64_t blocksize) {
61 struct btrfs_ioctl_vol_args args = {};
62 int r;
63
64 assert((uint64_t) (int) blocksize == blocksize);
65
66 /* https://bugzilla.kernel.org/show_bug.cgi?id=118111 */
67 if (numblocks * blocksize < 256*1024*1024) {
68 log_warning("%s: resizing of btrfs volumes smaller than 256M is not supported", path);
69 return -EOPNOTSUPP;
70 }
71
72 r = snprintf(args.name, sizeof(args.name), "%"PRIu64, numblocks * blocksize);
73 /* The buffer is large enough for any number to fit... */
74 assert((size_t) r < sizeof(args.name));
75
76 if (arg_dry_run)
77 return 0;
78
79 if (ioctl(mountfd, BTRFS_IOC_RESIZE, &args) != 0)
80 return log_error_errno(errno, "Failed to resize \"%s\" to %"PRIu64" blocks (btrfs): %m",
81 path, numblocks);
82
83 return 0;
84 }
85
86 static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_devno) {
87 char devpath[DEV_NUM_PATH_MAX], main_devpath[DEV_NUM_PATH_MAX];
88 _cleanup_close_ int main_devfd = -1;
89 _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
90 uint64_t size;
91 int r;
92
93 xsprintf_dev_num_path(main_devpath, "block", main_devno);
94 main_devfd = open(main_devpath, O_RDONLY|O_CLOEXEC);
95 if (main_devfd < 0)
96 return log_error_errno(errno, "Failed to open \"%s\": %m", main_devpath);
97
98 if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0)
99 return log_error_errno(errno, "Failed to query size of \"%s\" (before resize): %m",
100 main_devpath);
101
102 log_debug("%s is %"PRIu64" bytes", main_devpath, size);
103
104 xsprintf_dev_num_path(devpath, "block", devno);
105 r = crypt_init(&cd, devpath);
106 if (r < 0)
107 return log_error_errno(r, "crypt_init(\"%s\") failed: %m", devpath);
108
109 crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
110
111 r = crypt_load(cd, CRYPT_LUKS, NULL);
112 if (r < 0)
113 return log_debug_errno(r, "Failed to load LUKS metadata for %s: %m", devpath);
114
115 if (arg_dry_run)
116 return 0;
117
118 r = crypt_resize(cd, main_devpath, 0);
119 if (r < 0)
120 return log_error_errno(r, "crypt_resize() of %s failed: %m", devpath);
121
122 if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0)
123 log_warning_errno(errno, "Failed to query size of \"%s\" (after resize): %m",
124 devpath);
125 else
126 log_debug("%s is now %"PRIu64" bytes", main_devpath, size);
127
128 return 1;
129 }
130
131 static int maybe_resize_slave_device(const char *mountpath, dev_t main_devno) {
132 dev_t devno;
133 char devpath[DEV_NUM_PATH_MAX];
134 _cleanup_free_ char *fstype = NULL;
135 int r;
136
137 crypt_set_log_callback(NULL, cryptsetup_log_glue, NULL);
138 crypt_set_debug_level(1);
139
140 r = get_block_device_harder(mountpath, &devno);
141 if (r < 0)
142 return log_error_errno(r, "Failed to determine underlying block device of \"%s\": %m",
143 mountpath);
144
145 log_debug("Underlying device %d:%d, main dev %d:%d, %s",
146 major(devno), minor(devno),
147 major(main_devno), minor(main_devno),
148 devno == main_devno ? "same" : "different");
149 if (devno == main_devno)
150 return 0;
151
152 xsprintf_dev_num_path(devpath, "block", devno);
153 r = probe_filesystem(devpath, &fstype);
154 if (r < 0)
155 return log_warning_errno(r, "Failed to probe \"%s\": %m", devpath);
156
157 if (streq_ptr(fstype, "crypto_LUKS"))
158 return resize_crypt_luks_device(devno, fstype, main_devno);
159
160 log_debug("Don't know how to resize %s of type %s, ignoring", devpath, strnull(fstype));
161 return 0;
162 }
163
164 static void help(void) {
165 printf("%s [OPTIONS...] /path/to/mountpoint\n\n"
166 "Grow filesystem or encrypted payload to device size.\n\n"
167 "Options:\n"
168 " -h --help Show this help and exit\n"
169 " --version Print version string and exit\n"
170 " -n --dry-run Just print what would be done\n"
171 , program_invocation_short_name);
172 }
173
174 static int parse_argv(int argc, char *argv[]) {
175 enum {
176 ARG_VERSION = 0x100,
177 };
178
179 int c;
180
181 static const struct option options[] = {
182 { "help", no_argument, NULL, 'h' },
183 { "version" , no_argument, NULL, ARG_VERSION },
184 { "dry-run", no_argument, NULL, 'n' },
185 {}
186 };
187
188 assert(argc >= 0);
189 assert(argv);
190
191 while ((c = getopt_long(argc, argv, "hn", options, NULL)) >= 0)
192 switch(c) {
193 case 'h':
194 help();
195 return 0;
196
197 case ARG_VERSION:
198 version();
199 return 0;
200
201 case 'n':
202 arg_dry_run = true;
203 break;
204
205 case '?':
206 return -EINVAL;
207
208 default:
209 assert_not_reached("Unhandled option");
210 }
211
212 if (optind + 1 != argc) {
213 log_error("%s excepts exactly one argument (the mount point).",
214 program_invocation_short_name);
215 return -EINVAL;
216 }
217
218 arg_target = argv[optind];
219
220 return 1;
221 }
222
223 int main(int argc, char *argv[]) {
224 dev_t devno;
225 _cleanup_close_ int mountfd = -1, devfd = -1;
226 int blocksize;
227 uint64_t size, numblocks;
228 char devpath[DEV_NUM_PATH_MAX], fb[FORMAT_BYTES_MAX];
229 struct statfs sfs;
230 int r;
231
232 log_set_target(LOG_TARGET_AUTO);
233 log_parse_environment();
234 log_open();
235
236 r = parse_argv(argc, argv);
237 if (r < 0)
238 return EXIT_FAILURE;
239 if (r == 0)
240 return EXIT_SUCCESS;
241
242 r = path_is_mount_point(arg_target, NULL, 0);
243 if (r < 0) {
244 log_error_errno(r, "Failed to check if \"%s\" is a mount point: %m", arg_target);
245 return EXIT_FAILURE;
246 }
247 if (r == 0) {
248 log_error_errno(r, "\"%s\" is not a mount point: %m", arg_target);
249 return EXIT_FAILURE;
250 }
251
252 r = get_block_device(arg_target, &devno);
253 if (r < 0) {
254 log_error_errno(r, "Failed to determine block device of \"%s\": %m", arg_target);
255 return EXIT_FAILURE;
256 }
257
258 r = maybe_resize_slave_device(arg_target, devno);
259 if (r < 0)
260 return EXIT_FAILURE;
261
262 mountfd = open(arg_target, O_RDONLY|O_CLOEXEC);
263 if (mountfd < 0) {
264 log_error_errno(errno, "Failed to open \"%s\": %m", arg_target);
265 return EXIT_FAILURE;
266 }
267
268 xsprintf_dev_num_path(devpath, "block", devno);
269 devfd = open(devpath, O_RDONLY|O_CLOEXEC);
270 if (devfd < 0) {
271 log_error_errno(errno, "Failed to open \"%s\": %m", devpath);
272 return EXIT_FAILURE;
273 }
274
275 if (ioctl(devfd, BLKBSZGET, &blocksize) != 0) {
276 log_error_errno(errno, "Failed to query block size of \"%s\": %m", devpath);
277 return EXIT_FAILURE;
278 }
279
280 if (ioctl(devfd, BLKGETSIZE64, &size) != 0) {
281 log_error_errno(errno, "Failed to query size of \"%s\": %m", devpath);
282 return EXIT_FAILURE;
283 }
284
285 if (size % blocksize != 0)
286 log_notice("Partition size %"PRIu64" is not a multiple of the blocksize %d,"
287 " ignoring %"PRIu64" bytes", size, blocksize, size % blocksize);
288
289 numblocks = size / blocksize;
290
291 if (fstatfs(mountfd, &sfs) < 0) {
292 log_error_errno(errno, "Failed to stat file system \"%s\": %m", arg_target);
293 return EXIT_FAILURE;
294 }
295
296 switch(sfs.f_type) {
297 case EXT4_SUPER_MAGIC:
298 r = resize_ext4(arg_target, mountfd, devfd, numblocks, blocksize);
299 break;
300 case BTRFS_SUPER_MAGIC:
301 r = resize_btrfs(arg_target, mountfd, devfd, numblocks, blocksize);
302 break;
303 default:
304 log_error("Don't know how to resize fs %llx on \"%s\"",
305 (long long unsigned) sfs.f_type, arg_target);
306 return EXIT_FAILURE;
307 }
308
309 if (r < 0)
310 return EXIT_FAILURE;
311
312 log_info("Successfully resized \"%s\" to %s bytes (%"PRIu64" blocks of %d bytes).",
313 arg_target, format_bytes(fb, sizeof fb, size), numblocks, blocksize);
314 return EXIT_SUCCESS;
315 }