]> git.sesse.net Git - bcachefs-tools-debian/blob - make-bcache.c
6ffe89e1965ef5fbd2dd11b0f9430502a0d51545
[bcachefs-tools-debian] / make-bcache.c
1 /*
2  * Author: Kent Overstreet <kmo@daterainc.com>
3  *
4  * GPLv2
5  */
6
7 #define _FILE_OFFSET_BITS       64
8 #define __USE_FILE_OFFSET64
9 #define _XOPEN_SOURCE 600
10
11 #include <ctype.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <getopt.h>
15 #include <limits.h>
16 #include <linux/fs.h>
17 #include <stdbool.h>
18 #include <stdint.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/ioctl.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <uuid/uuid.h>
27
28 #include "bcache.h"
29
30 #define max(x, y) ({                            \
31         typeof(x) _max1 = (x);                  \
32         typeof(y) _max2 = (y);                  \
33         (void) (&_max1 == &_max2);              \
34         _max1 > _max2 ? _max1 : _max2; })
35
36 uint64_t getblocks(int fd)
37 {
38         uint64_t ret;
39         struct stat statbuf;
40         if (fstat(fd, &statbuf)) {
41                 perror("stat error\n");
42                 exit(EXIT_FAILURE);
43         }
44         ret = statbuf.st_size / 512;
45         if (S_ISBLK(statbuf.st_mode))
46                 if (ioctl(fd, BLKGETSIZE, &ret)) {
47                         perror("ioctl error");
48                         exit(EXIT_FAILURE);
49                 }
50         return ret;
51 }
52
53 uint64_t hatoi(const char *s)
54 {
55         char *e;
56         long long i = strtoll(s, &e, 10);
57         switch (*e) {
58                 case 't':
59                 case 'T':
60                         i *= 1024;
61                 case 'g':
62                 case 'G':
63                         i *= 1024;
64                 case 'm':
65                 case 'M':
66                         i *= 1024;
67                 case 'k':
68                 case 'K':
69                         i *= 1024;
70         }
71         return i;
72 }
73
74 unsigned hatoi_validate(const char *s, const char *msg)
75 {
76         uint64_t v = hatoi(s);
77
78         if (v & (v - 1)) {
79                 printf("%s must be a power of two\n", msg);
80                 exit(EXIT_FAILURE);
81         }
82
83         v /= 512;
84
85         if (v > USHRT_MAX) {
86                 printf("%s too large\n", msg);
87                 exit(EXIT_FAILURE);
88         }
89
90         if (!v) {
91                 printf("%s too small\n", msg);
92                 exit(EXIT_FAILURE);
93         }
94
95         return v;
96 }
97
98 char *skip_spaces(const char *str)
99 {
100         while (isspace(*str))
101                 ++str;
102         return (char *)str;
103 }
104
105 char *strim(char *s)
106 {
107         size_t size;
108         char *end;
109
110         s = skip_spaces(s);
111         size = strlen(s);
112         if (!size)
113                 return s;
114
115         end = s + size - 1;
116         while (end >= s && isspace(*end))
117                 end--;
118         *(end + 1) = '\0';
119
120         return s;
121 }
122
123 ssize_t read_string_list(const char *buf, const char * const list[])
124 {
125         size_t i;
126         char *s, *d = strdup(buf);
127         if (!d)
128                 return -ENOMEM;
129
130         s = strim(d);
131
132         for (i = 0; list[i]; i++)
133                 if (!strcmp(list[i], s))
134                         break;
135
136         free(d);
137
138         if (!list[i])
139                 return -EINVAL;
140
141         return i;
142 }
143
144 void usage()
145 {
146         printf("Usage: make-bcache [options] device\n"
147                "        -C, --cache             Format a cache device\n"
148                "        -B, --bdev              Format a backing device\n"
149                "        -b, --bucket            bucket size\n"
150                "        -w, --block             block size (hard sector size of SSD, often 2k)\n"
151                "        -o, --data-offset       data offset in sectors\n"
152                "            --cset-uuid         UUID for the cache set\n"
153 //             "        -U                      UUID\n"
154                "            --writeback         enable writeback\n"
155                "            --discard           enable discards\n"
156                "            --cache_replacement_policy=(lru|fifo)\n"
157                "        -h, --help              display this help and exit\n");
158         exit(EXIT_FAILURE);
159 }
160
161 const char * const cache_replacement_policies[] = {
162         "lru",
163         "fifo",
164         "random",
165         NULL
166 };
167
168 static void write_sb(char *dev, unsigned block_size, unsigned bucket_size,
169                      bool writeback, bool discard,
170                      unsigned cache_replacement_policy,
171                      uint64_t data_offset,
172                      uuid_t set_uuid, bool bdev)
173 {
174         int fd;
175         char uuid_str[40], set_uuid_str[40];
176         struct cache_sb sb;
177
178         if ((fd = open(dev, O_RDWR|O_EXCL)) == -1) {
179                 printf("Can't open dev %s: %s\n", dev, strerror(errno));
180                 exit(EXIT_FAILURE);
181         }
182
183         memset(&sb, 0, sizeof(struct cache_sb));
184
185         sb.offset       = SB_SECTOR;
186         sb.version      = bdev
187                 ? BCACHE_SB_VERSION_BDEV
188                 : BCACHE_SB_VERSION_CDEV;
189
190         memcpy(sb.magic, bcache_magic, 16);
191         uuid_generate(sb.uuid);
192         memcpy(sb.set_uuid, set_uuid, sizeof(sb.set_uuid));
193
194         sb.bucket_size  = bucket_size;
195         sb.block_size   = block_size;
196
197         uuid_unparse(sb.uuid, uuid_str);
198         uuid_unparse(sb.set_uuid, set_uuid_str);
199
200         if (SB_IS_BDEV(&sb)) {
201                 SET_BDEV_CACHE_MODE(
202                         &sb, writeback ? CACHE_MODE_WRITEBACK : CACHE_MODE_WRITETHROUGH);
203
204                 if (data_offset != BDEV_DATA_START_DEFAULT) {
205                         sb.version = BCACHE_SB_VERSION_BDEV_WITH_OFFSET;
206                         sb.data_offset = data_offset;
207                 }
208
209                 printf("UUID:                   %s\n"
210                        "Set UUID:               %s\n"
211                        "version:                %u\n"
212                        "block_size:             %u\n"
213                        "data_offset:            %ju\n",
214                        uuid_str, set_uuid_str,
215                        (unsigned) sb.version,
216                        sb.block_size,
217                        data_offset);
218         } else {
219                 sb.nbuckets             = getblocks(fd) / sb.bucket_size;
220                 sb.nr_in_set            = 1;
221                 sb.first_bucket         = (23 / sb.bucket_size) + 1;
222
223                 if (sb.nbuckets < 1 << 7) {
224                         printf("Not enough buckets: %ju, need %u\n",
225                                sb.nbuckets, 1 << 7);
226                         exit(EXIT_FAILURE);
227                 }
228
229                 SET_CACHE_DISCARD(&sb, discard);
230                 SET_CACHE_REPLACEMENT(&sb, cache_replacement_policy);
231
232                 printf("UUID:                   %s\n"
233                        "Set UUID:               %s\n"
234                        "version:                %u\n"
235                        "nbuckets:               %ju\n"
236                        "block_size:             %u\n"
237                        "bucket_size:            %u\n"
238                        "nr_in_set:              %u\n"
239                        "nr_this_dev:            %u\n"
240                        "first_bucket:           %u\n",
241                        uuid_str, set_uuid_str,
242                        (unsigned) sb.version,
243                        sb.nbuckets,
244                        sb.block_size,
245                        sb.bucket_size,
246                        sb.nr_in_set,
247                        sb.nr_this_dev,
248                        sb.first_bucket);
249         }
250
251         sb.csum = csum_set(&sb);
252
253         if (pwrite(fd, &sb, sizeof(sb), SB_SECTOR << 9) != sizeof(sb)) {
254                 perror("write error\n");
255                 exit(EXIT_FAILURE);
256         }
257
258         fsync(fd);
259         close(fd);
260 }
261
262 static unsigned get_blocksize(const char *path)
263 {
264         struct stat statbuf;
265
266         if (stat(path, &statbuf)) {
267                 fprintf(stderr, "Error statting %s: %s\n",
268                         path, strerror(errno));
269                 exit(EXIT_FAILURE);
270         }
271
272         return statbuf.st_blksize / 512;
273 }
274
275 int main(int argc, char **argv)
276 {
277         int c, bdev = -1;
278         unsigned i, ncache_devices = 0, nbacking_devices = 0;
279         char *cache_devices[argc];
280         char *backing_devices[argc];
281
282         unsigned block_size = 0, bucket_size = 1024;
283         int writeback = 0, discard = 0;
284         unsigned cache_replacement_policy = 0;
285         uint64_t data_offset = BDEV_DATA_START_DEFAULT;
286         uuid_t set_uuid;
287
288         uuid_generate(set_uuid);
289
290         struct option opts[] = {
291                 { "cache",              0, NULL,        'C' },
292                 { "bdev",               0, NULL,        'B' },
293                 { "bucket",             1, NULL,        'b' },
294                 { "block",              1, NULL,        'w' },
295                 { "writeback",          0, &writeback,  1 },
296                 { "discard",            0, &discard,    1 },
297                 { "cache_replacement_policy", 1, NULL, 'p' },
298                 { "data_offset",        1, NULL,        'o' },
299                 { "cset-uuid",          1, NULL,        'u' },
300                 { "help",               0, NULL,        'h' },
301                 { NULL,                 0, NULL,        0 },
302         };
303
304         while ((c = getopt_long(argc, argv,
305                                 "-hCBU:w:b:",
306                                 opts, NULL)) != -1)
307                 switch (c) {
308                 case 'C':
309                         bdev = 0;
310                         break;
311                 case 'B':
312                         bdev = 1;
313                         break;
314                 case 'b':
315                         bucket_size = hatoi_validate(optarg, "bucket size");
316                         break;
317                 case 'w':
318                         block_size = hatoi_validate(optarg, "block size");
319                         break;
320 #if 0
321                 case 'U':
322                         if (uuid_parse(optarg, sb.uuid)) {
323                                 printf("Bad uuid\n");
324                                 exit(EXIT_FAILURE);
325                         }
326                         break;
327 #endif
328                 case 'p':
329                         cache_replacement_policy = read_string_list(optarg,
330                                                     cache_replacement_policies);
331                         break;
332                 case 'o':
333                         data_offset = atoll(optarg);
334                         if (data_offset < BDEV_DATA_START_DEFAULT) {
335                                 printf("Bad data offset; minimum %d sectors\n",
336                                        BDEV_DATA_START_DEFAULT);
337                                 exit(EXIT_FAILURE);
338                         }
339                         break;
340                 case 'u':
341                         if (uuid_parse(optarg, set_uuid)) {
342                                 printf("Bad uuid\n");
343                                 exit(EXIT_FAILURE);
344                         }
345                         break;
346                 case 'h':
347                         usage();
348                         break;
349                 case 1:
350                         if (bdev == -1) {
351                                 printf("Please specify -C or -B\n");
352                                 exit(EXIT_FAILURE);
353                         }
354
355                         if (bdev)
356                                 backing_devices[nbacking_devices++] = optarg;
357                         else
358                                 cache_devices[ncache_devices++] = optarg;
359                         break;
360                 }
361
362         if (!ncache_devices && !nbacking_devices) {
363                 printf("Please supply a device\n");
364                 usage();
365         }
366
367         if (bucket_size < block_size) {
368                 printf("Bucket size cannot be smaller than block size\n");
369                 exit(EXIT_FAILURE);
370         }
371
372         if (!block_size) {
373                 for (i = 0; i < ncache_devices; i++)
374                         block_size = max(block_size,
375                                          get_blocksize(cache_devices[i]));
376
377                 for (i = 0; i < nbacking_devices; i++)
378                         block_size = max(block_size,
379                                          get_blocksize(backing_devices[i]));
380         }
381
382         for (i = 0; i < ncache_devices; i++)
383                 write_sb(cache_devices[i], block_size, bucket_size,
384                          writeback, discard, cache_replacement_policy,
385                          data_offset, set_uuid, false);
386
387         for (i = 0; i < nbacking_devices; i++)
388                 write_sb(backing_devices[i], block_size, bucket_size,
389                          writeback, discard, cache_replacement_policy,
390                          data_offset, set_uuid, true);
391
392         return 0;
393 }