]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/partition/growfs.c
growfs: add support for resizing encrypted partitions
[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 <linux/magic.h>
24 #include <sys/ioctl.h>
25 #include <sys/mount.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/vfs.h>
29
30 #include "crypt-util.h"
31 #include "device-nodes.h"
32 #include "dissect-image.h"
33 #include "escape.h"
34 #include "fd-util.h"
35 #include "format-util.h"
36 #include "log.h"
37 #include "missing.h"
38 #include "mount-util.h"
39 #include "parse-util.h"
40 #include "path-util.h"
41 #include "strv.h"
42
43 static int resize_ext4(const char *path, int mountfd, int devfd, uint64_t numblocks, uint64_t blocksize) {
44 assert((uint64_t) (int) blocksize == blocksize);
45
46 if (ioctl(mountfd, EXT4_IOC_RESIZE_FS, &numblocks) != 0)
47 return log_error_errno(errno, "Failed to resize \"%s\" to %"PRIu64" blocks (ext4): %m",
48 path, numblocks);
49
50 return 0;
51 }
52
53 static int resize_btrfs(const char *path, int mountfd, int devfd, uint64_t numblocks, uint64_t blocksize) {
54 struct btrfs_ioctl_vol_args args = {};
55 int r;
56
57 assert((uint64_t) (int) blocksize == blocksize);
58
59 /* https://bugzilla.kernel.org/show_bug.cgi?id=118111 */
60 if (numblocks * blocksize < 256*1024*1024) {
61 log_warning("%s: resizing of btrfs volumes smaller than 256M is not supported", path);
62 return -EOPNOTSUPP;
63 }
64
65 r = snprintf(args.name, sizeof(args.name), "%"PRIu64, numblocks * blocksize);
66 /* The buffer is large enough for any number to fit... */
67 assert((size_t) r < sizeof(args.name));
68
69 if (ioctl(mountfd, BTRFS_IOC_RESIZE, &args) != 0)
70 return log_error_errno(errno, "Failed to resize \"%s\" to %"PRIu64" blocks (btrfs): %m",
71 path, numblocks);
72
73 return 0;
74 }
75
76 static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_devno) {
77 char devpath[DEV_NUM_PATH_MAX], main_devpath[DEV_NUM_PATH_MAX];
78 _cleanup_close_ int main_devfd = -1;
79 _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
80 uint64_t size;
81 int r;
82
83 xsprintf_dev_num_path(main_devpath, "block", main_devno);
84 main_devfd = open(main_devpath, O_RDONLY|O_CLOEXEC);
85 if (main_devfd < 0)
86 return log_error_errno(errno, "Failed to open \"%s\": %m", main_devpath);
87
88 if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0)
89 return log_error_errno(errno, "Failed to query size of \"%s\" (before resize): %m",
90 main_devpath);
91
92 log_debug("%s is %"PRIu64" bytes", main_devpath, size);
93
94 xsprintf_dev_num_path(devpath, "block", devno);
95 r = crypt_init(&cd, devpath);
96 if (r < 0)
97 return log_error_errno(r, "crypt_init(\"%s\") failed: %m", devpath);
98
99 crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
100
101 r = crypt_load(cd, CRYPT_LUKS, NULL);
102 if (r < 0)
103 return log_debug_errno(r, "Failed to load LUKS metadata for %s: %m", devpath);
104
105 r = crypt_resize(cd, main_devpath, 0);
106 if (r < 0)
107 return log_error_errno(r, "crypt_resize() of %s failed: %m", devpath);
108
109 if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0)
110 log_warning_errno(errno, "Failed to query size of \"%s\" (after resize): %m",
111 devpath);
112 else
113 log_debug("%s is now %"PRIu64" bytes", main_devpath, size);
114
115 return 1;
116 }
117
118 static int maybe_resize_slave_device(const char *mountpath, dev_t main_devno) {
119 dev_t devno;
120 char devpath[DEV_NUM_PATH_MAX];
121 _cleanup_free_ char *fstype = NULL;
122 int r;
123
124 crypt_set_log_callback(NULL, cryptsetup_log_glue, NULL);
125 crypt_set_debug_level(1);
126
127 r = get_block_device_harder(mountpath, &devno);
128 if (r < 0)
129 return log_error_errno(r, "Failed to determine underlying block device of \"%s\": %m",
130 mountpath);
131
132 log_debug("Underlying device %d:%d, main dev %d:%d, %s",
133 major(devno), minor(devno),
134 major(main_devno), minor(main_devno),
135 devno == main_devno ? "same" : "different");
136 if (devno == main_devno)
137 return 0;
138
139 xsprintf_dev_num_path(devpath, "block", devno);
140 r = probe_filesystem(devpath, &fstype);
141 if (r < 0)
142 return log_warning_errno(r, "Failed to probe \"%s\": %m", devpath);
143
144 if (streq_ptr(fstype, "crypto_LUKS"))
145 return resize_crypt_luks_device(devno, fstype, main_devno);
146
147 log_debug("Don't know how to resize %s of type %s, ignoring", devpath, strnull(fstype));
148 return 0;
149 }
150
151 int main(int argc, char *argv[]) {
152 dev_t devno;
153 _cleanup_close_ int mountfd = -1, devfd = -1;
154 int blocksize;
155 uint64_t size, numblocks;
156 char devpath[DEV_NUM_PATH_MAX], fb[FORMAT_BYTES_MAX];
157 struct statfs sfs;
158 int r;
159
160 if (argc != 2) {
161 log_error("This program requires one argument (the mountpoint).");
162 return EXIT_FAILURE;
163 }
164
165 log_set_target(LOG_TARGET_AUTO);
166 log_parse_environment();
167 log_open();
168
169 r = path_is_mount_point(argv[1], NULL, 0);
170 if (r < 0) {
171 log_error_errno(r, "Failed to check if \"%s\" is a mount point: %m", argv[1]);
172 return EXIT_FAILURE;
173 }
174 if (r == 0) {
175 log_error_errno(r, "\"%s\" is not a mount point: %m", argv[1]);
176 return EXIT_FAILURE;
177 }
178
179 r = get_block_device(argv[1], &devno);
180 if (r < 0) {
181 log_error_errno(r, "Failed to determine block device of \"%s\": %m", argv[1]);
182 return EXIT_FAILURE;
183 }
184
185 r = maybe_resize_slave_device(argv[1], devno);
186 if (r < 0)
187 return EXIT_FAILURE;
188
189 mountfd = open(argv[1], O_RDONLY|O_CLOEXEC);
190 if (mountfd < 0) {
191 log_error_errno(errno, "Failed to open \"%s\": %m", argv[1]);
192 return EXIT_FAILURE;
193 }
194
195 xsprintf_dev_num_path(devpath, "block", devno);
196 devfd = open(devpath, O_RDONLY|O_CLOEXEC);
197 if (devfd < 0) {
198 log_error_errno(errno, "Failed to open \"%s\": %m", devpath);
199 return EXIT_FAILURE;
200 }
201
202 if (ioctl(devfd, BLKBSZGET, &blocksize) != 0) {
203 log_error_errno(errno, "Failed to query block size of \"%s\": %m", devpath);
204 return EXIT_FAILURE;
205 }
206
207 if (ioctl(devfd, BLKGETSIZE64, &size) != 0) {
208 log_error_errno(errno, "Failed to query size of \"%s\": %m", devpath);
209 return EXIT_FAILURE;
210 }
211
212 if (size % blocksize != 0)
213 log_notice("Partition size %"PRIu64" is not a multiple of the blocksize %d,"
214 " ignoring %"PRIu64" bytes", size, blocksize, size % blocksize);
215
216 numblocks = size / blocksize;
217
218 if (fstatfs(mountfd, &sfs) < 0) {
219 log_error_errno(errno, "Failed to stat file system \"%s\": %m", argv[1]);
220 return EXIT_FAILURE;
221 }
222
223 switch(sfs.f_type) {
224 case EXT4_SUPER_MAGIC:
225 r = resize_ext4(argv[1], mountfd, devfd, numblocks, blocksize);
226 break;
227 case BTRFS_SUPER_MAGIC:
228 r = resize_btrfs(argv[1], mountfd, devfd, numblocks, blocksize);
229 break;
230 default:
231 log_error("Don't know how to resize fs %llx on \"%s\"",
232 (long long unsigned) sfs.f_type, argv[1]);
233 return EXIT_FAILURE;
234 }
235
236 if (r < 0)
237 return EXIT_FAILURE;
238
239 log_info("Successfully resized \"%s\" to %s bytes (%"PRIu64" blocks of %d bytes).",
240 argv[1], format_bytes(fb, sizeof fb, size), numblocks, blocksize);
241 return EXIT_SUCCESS;
242 }