380312bca1bb1b4fcf83590843717b0274315b21
[oweals/busybox.git] / util-linux / mkfs_ext2.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * mkfs_ext2: utility to create EXT2 filesystem
4  * inspired by genext2fs
5  *
6  * Busybox'ed (2009) by Vladimir Dronnikov <dronnikov@gmail.com>
7  *
8  * Licensed under GPLv2, see file LICENSE in this tarball for details.
9  */
10 #include "libbb.h"
11 #include <linux/fs.h>
12 #include <linux/ext2_fs.h>
13 #include <sys/user.h> /* PAGE_SIZE */
14 #ifndef PAGE_SIZE
15 # define PAGE_SIZE 4096
16 #endif
17 #include "volume_id/volume_id_internal.h"
18
19 #define ENABLE_FEATURE_MKFS_EXT2_RESERVED_GDT 0
20 #define ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX 1
21
22 // from e2fsprogs
23 #define s_reserved_gdt_blocks s_padding1
24 #define s_mkfs_time           s_reserved[0]
25 #define s_flags               s_reserved[22]
26 #define EXT2_HASH_HALF_MD4     1
27 #define EXT2_FLAGS_SIGNED_HASH 0x0001
28
29 // whiteout: for writable overlays
30 //#define LINUX_S_IFWHT                  0160000
31 //#define EXT2_FEATURE_INCOMPAT_WHITEOUT 0x0020
32
33 // storage helpers
34 char BUG_wrong_field_size(void);
35 #define STORE_LE(field, value) \
36 do { \
37         if (sizeof(field) == 4) \
38                 field = cpu_to_le32(value); \
39         else if (sizeof(field) == 2) \
40                 field = cpu_to_le16(value); \
41         else if (sizeof(field) == 1) \
42                 field = (value); \
43         else \
44                 BUG_wrong_field_size(); \
45 } while (0)
46
47 #define FETCH_LE32(field) \
48         (sizeof(field) == 4 ? cpu_to_le32(field) : BUG_wrong_field_size())
49
50 // All fields are little-endian
51 struct ext2_dir {
52         uint32_t inode1;
53         uint16_t rec_len1;
54         uint8_t  name_len1;
55         uint8_t  file_type1;
56         char     name1[4];
57         uint32_t inode2;
58         uint16_t rec_len2;
59         uint8_t  name_len2;
60         uint8_t  file_type2;
61         char     name2[4];
62         uint32_t inode3;
63         uint16_t rec_len3;
64         uint8_t  name_len3;
65         uint8_t  file_type3;
66         char     name3[12];
67 };
68
69 static unsigned int_log2(unsigned arg)
70 {
71         unsigned r = 0;
72         while ((arg >>= 1) != 0)
73                 r++;
74         return r;
75 }
76
77 // taken from mkfs_minix.c. libbb candidate?
78 // why "uint64_t size"? we never use it for anything >32 bits
79 static uint32_t div_roundup(uint64_t size, uint32_t n)
80 {
81         // Overflow-resistant
82         uint32_t res = size / n;
83         if (res * n != size)
84                 res++;
85         return res;
86 }
87
88 static void allocate(uint8_t *bitmap, uint32_t blocksize, uint32_t start, uint32_t end)
89 {
90         uint32_t i;
91
92 //bb_info_msg("ALLOC: [%u][%u][%u]: [%u-%u]:=[%x],[%x]", blocksize, start, end, start/8, blocksize - end/8 - 1, (1 << (start & 7)) - 1, (uint8_t)(0xFF00 >> (end & 7)));
93         memset(bitmap, 0, blocksize);
94         i = start / 8;
95         memset(bitmap, 0xFF, i);
96         bitmap[i] = (1 << (start & 7)) - 1; //0..7 => 00000000..01111111
97         i = end / 8;
98         bitmap[blocksize - i - 1] |= 0x7F00 >> (end & 7); //0..7 => 00000000..11111110
99         memset(bitmap + blocksize - i, 0xFF, i); // N.B. no overflow here!
100 }
101
102 static uint32_t has_super(uint32_t x)
103 {
104         // 0, 1 and powers of 3, 5, 7 up to 2^32 limit
105         static const uint32_t supers[] = {
106                 0, 1, 3, 5, 7, 9, 25, 27, 49, 81, 125, 243, 343, 625, 729,
107                 2187, 2401, 3125, 6561, 15625, 16807, 19683, 59049, 78125,
108                 117649, 177147, 390625, 531441, 823543, 1594323, 1953125,
109                 4782969, 5764801, 9765625, 14348907, 40353607, 43046721,
110                 48828125, 129140163, 244140625, 282475249, 387420489,
111                 1162261467, 1220703125, 1977326743, 3486784401/* >2^31 */,
112         };
113         const uint32_t *sp = supers + ARRAY_SIZE(supers);
114         while (1) {
115                 sp--;
116                 if (x == *sp)
117                         return 1;
118                 if (x > *sp)
119                         return 0;
120         }
121 }
122
123 /* Standard mke2fs 1.41.9:
124  * Usage: mke2fs [-c|-l filename] [-b block-size] [-f fragment-size]
125  *      [-i bytes-per-inode] [-I inode-size] [-J journal-options]
126  *      [-G meta group size] [-N number-of-inodes]
127  *      [-m reserved-blocks-percentage] [-o creator-os]
128  *      [-g blocks-per-group] [-L volume-label] [-M last-mounted-directory]
129  *      [-O feature[,...]] [-r fs-revision] [-E extended-option[,...]]
130  *      [-T fs-type] [-U UUID] [-jnqvFSV] device [blocks-count]
131 */
132 // N.B. not commented below options are taken and silently ignored
133 enum {
134         OPT_c = 1 << 0,
135         OPT_l = 1 << 1,
136         OPT_b = 1 << 2,         // block size, in bytes
137         OPT_f = 1 << 3,
138         OPT_i = 1 << 4,         // bytes per inode
139         OPT_I = 1 << 5,
140         OPT_J = 1 << 6,
141         OPT_G = 1 << 7,
142         OPT_N = 1 << 8,
143         OPT_m = 1 << 9,         // percentage of blocks reserved for superuser
144         OPT_o = 1 << 10,
145         OPT_g = 1 << 11,
146         OPT_L = 1 << 12,        // label
147         OPT_M = 1 << 13,
148         OPT_O = 1 << 14,
149         OPT_r = 1 << 15,
150         OPT_E = 1 << 16,
151         OPT_T = 1 << 17,
152         OPT_U = 1 << 18,
153         OPT_j = 1 << 19,
154         OPT_n = 1 << 20,        // dry run: do not write anything
155         OPT_q = 1 << 21,
156         OPT_v = 1 << 22,
157         OPT_F = 1 << 23,
158         OPT_S = 1 << 24,
159         //OPT_V = 1 << 25,      // -V version. bbox applets don't support that
160 };
161
162 #define fd 3    /* predefined output descriptor */
163
164 static void PUT(uint64_t off, void *buf, uint32_t size)
165 {
166 //      bb_info_msg("PUT[%llu]:[%u]", off, size);
167         xlseek(fd, off, SEEK_SET);
168         xwrite(fd, buf, size);
169 }
170
171 int mkfs_ext2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
172 int mkfs_ext2_main(int argc UNUSED_PARAM, char **argv)
173 {
174         unsigned i, pos, n;
175         unsigned bs, bpi;
176         unsigned blocksize, blocksize_log2;
177         unsigned reserved_percent = 5;
178         unsigned long long kilobytes;
179         uint32_t nblocks, nblocks_full, nreserved;
180         uint32_t ngroups;
181         uint32_t bytes_per_inode;
182         uint32_t first_data_block;
183         uint32_t inodes_per_group;
184         uint32_t gdtsz, itsz;
185         time_t timestamp;
186         unsigned opts;
187         const char *label = "";
188         struct stat st;
189         struct ext2_super_block *sb; // superblock
190         struct ext2_group_desc *gd; // group descriptors
191         struct ext2_inode *inode;
192         struct ext2_dir *dir;
193         uint8_t *buf;
194
195         opt_complementary = "-1:b+:m+:i+";
196         opts = getopt32(argv, "cl:b:f:i:I:J:G:N:m:o:g:L:M:O:r:E:T:U:jnqvFS",
197                 NULL, &bs, NULL, &bpi, NULL, NULL, NULL, NULL,
198                 &reserved_percent, NULL, NULL, &label, NULL, NULL, NULL, NULL, NULL, NULL);
199         argv += optind; // argv[0] -- device
200
201         // check the device is a block device
202         xmove_fd(xopen(argv[0], O_WRONLY), fd);
203         fstat(fd, &st);
204         if (!S_ISBLK(st.st_mode) && !(opts & OPT_F))
205                 bb_error_msg_and_die("not a block device");
206
207         // check if it is mounted
208         // N.B. what if we format a file? find_mount_point will return false negative since
209         // it is loop block device which mounted!
210         if (find_mount_point(argv[0], 0))
211                 bb_error_msg_and_die("can't format mounted filesystem");
212
213         // open the device, get size in kbytes
214         if (argv[1]) {
215                 kilobytes = xatoull(argv[1]);
216         } else {
217                 kilobytes = (uoff_t)xlseek(fd, 0, SEEK_END) / 1024;
218         }
219
220         bytes_per_inode = 16384;
221         if (kilobytes < 512*1024)
222                 bytes_per_inode = 4096;
223         if (kilobytes < 3*1024)
224                 bytes_per_inode = 8192;
225         if (opts & OPT_i)
226                 bytes_per_inode = bpi;
227
228         // Determine block size
229         // block size is a multiple of 1024
230         blocksize = 1024;
231         if (kilobytes >= 512*1024) // mke2fs 1.41.9 compat
232                 blocksize = 4096;
233         if (EXT2_MAX_BLOCK_SIZE > 4096) {
234                 // kilobytes >> 22 == size in 4gigabyte chunks.
235                 // if size >= 16k gigs, blocksize must be increased.
236                 // Try "mke2fs -F image $((16 * 1024*1024*1024))"
237                 while ((kilobytes >> 22) >= blocksize)
238                         blocksize *= 2;
239         }
240         if (opts & OPT_b)
241                 blocksize = bs;
242         if (blocksize < EXT2_MIN_BLOCK_SIZE
243          || blocksize > EXT2_MAX_BLOCK_SIZE
244          || (blocksize & (blocksize - 1)) // not power of 2
245         ) {
246                 bb_error_msg_and_die("blocksize %u is bad", blocksize);
247         }
248         // number of bits in one block, i.e. 8*blocksize
249 #define blocks_per_group (8 * blocksize)
250         first_data_block = (EXT2_MIN_BLOCK_SIZE == blocksize);
251         blocksize_log2 = int_log2(blocksize);
252
253         // Determine number of blocks
254         kilobytes >>= (blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE);
255         nblocks = kilobytes;
256         if (nblocks != kilobytes)
257                 bb_error_msg_and_die("block count doesn't fit in 32 bits");
258 #define kilobytes kilobytes_unused_after_this
259 //compat problem
260 //      if (blocksize < PAGE_SIZE)
261 //              nblocks &= ~((PAGE_SIZE >> blocksize_log2)-1);
262         // Experimentally, standard mke2fs won't work on images smaller than 60k
263         if (nblocks < 60)
264                 bb_error_msg_and_die("need >= 60 blocks");
265
266         // How many reserved blocks?
267         if (reserved_percent > 50)
268                 bb_error_msg_and_die("-%c is bad", 'm');
269         //nreserved = div_roundup((uint64_t) nblocks * reserved_percent, 100);
270         nreserved = (uint64_t)nblocks * reserved_percent / 100;
271
272         // N.B. killing e2fsprogs feature! Unused blocks don't account in calculations
273         nblocks_full = nblocks;
274
275         // If last block group is too small, nblocks may be decreased in order
276         // to discard it, and control returns here to recalculate some
277         // parameters.
278         // Note: blocksize and bytes_per_inode are never recalculated.
279  retry:
280         // N.B. a block group can have no more than blocks_per_group blocks
281         ngroups = div_roundup(nblocks - first_data_block, blocks_per_group);
282         if (0 == ngroups)
283                 bb_error_msg_and_die("ngroups");
284
285         gdtsz = div_roundup(ngroups, blocksize / sizeof(*gd));
286         // TODO: reserved blocks must be marked as such in the bitmaps,
287         // or resulting filesystem is corrupt
288         if (ENABLE_FEATURE_MKFS_EXT2_RESERVED_GDT) {
289                 /*
290                  * From e2fsprogs: Calculate the number of GDT blocks to reserve for online
291                  * filesystem growth.
292                  * The absolute maximum number of GDT blocks we can reserve is determined by
293                  * the number of block pointers that can fit into a single block.
294                  * We set it at 1024x the current filesystem size, or
295                  * the upper block count limit (2^32), whichever is lower.
296                  */
297                 uint32_t rgdtsz = 0xFFFFFFFF; // maximum block number
298                 if (nblocks < rgdtsz / 1024)
299                         rgdtsz = nblocks * 1024;
300                 rgdtsz = div_roundup(rgdtsz - first_data_block, blocks_per_group);
301                 rgdtsz = div_roundup(rgdtsz, blocksize / sizeof(*gd)) - gdtsz;
302                 if (rgdtsz > blocksize / sizeof(uint32_t))
303                         rgdtsz = blocksize / sizeof(uint32_t);
304                 //TODO: STORE_LE(sb->s_reserved_gdt_blocks, rgdtsz);
305                 gdtsz += rgdtsz;
306         }
307
308         {
309                 // N.B. e2fsprogs does as follows!
310                 // ninodes is the total number of inodes (files) in the file system
311                 uint32_t ninodes = ((uint64_t) nblocks_full * blocksize) / bytes_per_inode;
312                 uint32_t overhead, remainder;
313                 if (ninodes < EXT2_GOOD_OLD_FIRST_INO+1)
314                         ninodes = EXT2_GOOD_OLD_FIRST_INO+1;
315                 inodes_per_group = div_roundup(ninodes, ngroups);
316                 // minimum number because the first EXT2_GOOD_OLD_FIRST_INO-1 are reserved
317                 if (inodes_per_group < 16)
318                         inodes_per_group = 16;
319
320                 // a block group can have no more than 8*blocksize inodes
321                 if (inodes_per_group > blocks_per_group)
322                         inodes_per_group = blocks_per_group;
323                 // adjust inodes per group so they completely fill the inode table blocks in the descriptor
324                 inodes_per_group = (div_roundup(inodes_per_group * sizeof(*inode), blocksize) * blocksize) / sizeof(*inode);
325                 // make sure the number of inodes per group is a multiple of 8
326                 inodes_per_group &= ~7;
327                 itsz = div_roundup(inodes_per_group * sizeof(*inode), blocksize);
328
329                 // the last group needs more attention: isn't it too small for possible overhead?
330                 overhead = (has_super(ngroups - 1) ? (1/*sb*/ + gdtsz) : 0) + 1/*bbmp*/ + 1/*ibmp*/ + itsz;
331                 remainder = (nblocks - first_data_block) % blocks_per_group;
332                 if ((1 == ngroups) && remainder && (remainder < overhead))
333                         bb_error_msg_and_die("way small device");
334                 if (remainder && (remainder < overhead + 50/* e2fsprogs hardcoded */)) {
335 //bb_info_msg("CHOP[%u]", remainder);
336                         nblocks -= remainder;
337                         goto retry;
338                 }
339         }
340
341         // print info
342         if (nblocks_full - nblocks)
343                 printf("warning: %u blocks unused\n\n", nblocks_full - nblocks);
344         printf(
345                 "Filesystem label=%s\n"
346                 "OS type: Linux\n"
347                 "Block size=%u (log=%u)\n"
348                 "Fragment size=%u (log=%u)\n"
349                 "%u inodes, %u blocks\n"
350                 "%u blocks (%u%%) reserved for the super user\n"
351                 "First data block=%u\n"
352                 "Maximum filesystem blocks=%u\n"
353                 "%u block groups\n"
354                 "%u blocks per group, %u fragments per group\n"
355                 "%u inodes per group"
356                 , label
357                 , blocksize, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE
358                 , blocksize, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE
359                 , inodes_per_group * ngroups, nblocks
360                 , nreserved, reserved_percent
361                 , first_data_block
362                 , gdtsz * (blocksize / sizeof(*gd)) * blocks_per_group
363                 , ngroups
364                 , blocks_per_group, blocks_per_group
365                 , inodes_per_group
366         );
367         {
368                 const char *fmt = "\nSuperblock backups stored on blocks:\n"
369                         "\t%u";
370                 pos = first_data_block;
371                 for (i = 1; i < ngroups; i++) {
372                         pos += blocks_per_group;
373                         if (has_super(i)) {
374                                 printf(fmt, (unsigned)pos);
375                                 fmt = ", %u";
376                         }
377                 }
378         }
379         bb_putchar('\n');
380
381         // dry run? -> we are done
382         if (opts & OPT_n)
383                 goto done;
384
385         // TODO: 3/5 refuse if mounted
386         // TODO: 4/5 compat options
387         // TODO: 1/5 sanity checks
388         // TODO: 0/5 more verbose error messages
389         // TODO: 4/5 bigendianness: recheck, wait for ARM reporters
390         // TODO: 2/5 reserved GDT: how to mark but not allocate?
391         // TODO: 3/5 dir_index?
392
393         // fill the superblock
394         sb = xzalloc(blocksize);
395         STORE_LE(sb->s_rev_level, 1); // revision 1 filesystem
396         STORE_LE(sb->s_magic, EXT2_SUPER_MAGIC);
397         STORE_LE(sb->s_inode_size, sizeof(*inode));
398         STORE_LE(sb->s_first_ino, EXT2_GOOD_OLD_FIRST_INO);
399         STORE_LE(sb->s_log_block_size, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE);
400         STORE_LE(sb->s_log_frag_size, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE);
401         // first 1024 bytes of the device are for boot record. If block size is 1024 bytes, then
402         // the first block available for data is 1, otherwise 0
403         STORE_LE(sb->s_first_data_block, first_data_block); // 0 or 1
404         // block and inode bitmaps occupy no more than one block, so maximum number of blocks is
405         STORE_LE(sb->s_blocks_per_group, blocks_per_group);
406         STORE_LE(sb->s_frags_per_group, blocks_per_group);
407         // blocks
408         STORE_LE(sb->s_blocks_count, nblocks);
409         // reserve blocks for superuser
410         STORE_LE(sb->s_r_blocks_count, nreserved);
411         // ninodes
412         STORE_LE(sb->s_inodes_per_group, inodes_per_group);
413         STORE_LE(sb->s_inodes_count, inodes_per_group * ngroups);
414         STORE_LE(sb->s_free_inodes_count, inodes_per_group * ngroups - EXT2_GOOD_OLD_FIRST_INO);
415         // timestamps
416         timestamp = time(NULL);
417         STORE_LE(sb->s_mkfs_time, timestamp);
418         STORE_LE(sb->s_wtime, timestamp);
419         STORE_LE(sb->s_lastcheck, timestamp);
420         // misc
421         STORE_LE(sb->s_state, 1); // TODO: what's 1?
422         STORE_LE(sb->s_creator_os, EXT2_OS_LINUX);
423         STORE_LE(sb->s_checkinterval, 24*60*60 * 180); // 180 days
424         STORE_LE(sb->s_errors, EXT2_ERRORS_DEFAULT);
425         STORE_LE(sb->s_feature_compat, EXT2_FEATURE_COMPAT_SUPP
426                 | (EXT2_FEATURE_COMPAT_RESIZE_INO * ENABLE_FEATURE_MKFS_EXT2_RESERVED_GDT)
427                 | (EXT2_FEATURE_COMPAT_DIR_INDEX * ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX)
428         );
429         // e2fsck from 1.41.9 doesn't like EXT2_FEATURE_INCOMPAT_WHITEOUT
430         STORE_LE(sb->s_feature_incompat, EXT2_FEATURE_INCOMPAT_FILETYPE);// | EXT2_FEATURE_INCOMPAT_WHITEOUT;
431         STORE_LE(sb->s_feature_ro_compat, EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER);
432         STORE_LE(sb->s_flags, EXT2_FLAGS_SIGNED_HASH * ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX);
433         generate_uuid(sb->s_uuid);
434         if (ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX) {
435                 STORE_LE(sb->s_def_hash_version, EXT2_HASH_HALF_MD4);
436                 generate_uuid((uint8_t *)sb->s_hash_seed);
437         }
438         /*
439          * From e2fsprogs: add "jitter" to the superblock's check interval so that we
440          * don't check all the filesystems at the same time.  We use a
441          * kludgy hack of using the UUID to derive a random jitter value.
442          */
443         STORE_LE(sb->s_max_mnt_count, EXT2_DFL_MAX_MNT_COUNT
444                 + (sb->s_uuid[ARRAY_SIZE(sb->s_uuid)-1] % EXT2_DFL_MAX_MNT_COUNT));
445
446         // write the label
447         safe_strncpy((char *)sb->s_volume_name, label, sizeof(sb->s_volume_name));
448
449         // fill group descriptors
450         gd = xzalloc(gdtsz * blocksize);
451         buf = xmalloc(blocksize);
452         sb->s_free_blocks_count = 0;
453         for (i = 0, pos = first_data_block, n = nblocks - first_data_block;
454                 i < ngroups;
455                 i++, pos += blocks_per_group, n -= blocks_per_group
456         ) {
457                 uint32_t overhead = pos + (has_super(i) ? (1/*sb*/ + gdtsz) : 0);
458                 uint32_t fb;
459                 STORE_LE(gd[i].bg_block_bitmap, overhead + 0);
460                 STORE_LE(gd[i].bg_inode_bitmap, overhead + 1);
461                 STORE_LE(gd[i].bg_inode_table, overhead + 2);
462                 overhead = overhead - pos + 1/*bbmp*/ + 1/*ibmp*/ + itsz;
463                 gd[i].bg_free_inodes_count = inodes_per_group;
464                 //STORE_LE(gd[i].bg_used_dirs_count, 0);
465                 // N.B. both root and lost+found dirs are within the first block group, thus +2
466                 if (0 == i) {
467                         overhead += 2;
468                         STORE_LE(gd[i].bg_used_dirs_count, 2);
469                         gd[i].bg_free_inodes_count -= EXT2_GOOD_OLD_FIRST_INO;
470                 }
471
472                 // mark preallocated blocks as allocated
473                 fb = (n < blocks_per_group ? n : blocks_per_group) - overhead;
474 //bb_info_msg("ALLOC: [%u][%u][%u]", blocksize, overhead, blocks_per_group - (fb + overhead));
475                 allocate(buf, blocksize,
476                         overhead,
477                         blocks_per_group - (fb + overhead)
478                 );
479                 // dump block bitmap
480                 PUT((uint64_t)(FETCH_LE32(gd[i].bg_block_bitmap)) * blocksize, buf, blocksize);
481                 STORE_LE(gd[i].bg_free_blocks_count, fb);
482
483                 // mark preallocated inodes as allocated
484                 allocate(buf, blocksize,
485                         inodes_per_group - gd[i].bg_free_inodes_count,
486                         blocks_per_group - inodes_per_group
487                 );
488                 // dump inode bitmap
489                 //PUT((uint64_t)(FETCH_LE32(gd[i].bg_block_bitmap)) * blocksize, buf, blocksize);
490                 //but it's right after block bitmap, so we can just:
491                 xwrite(fd, buf, blocksize);
492                 STORE_LE(gd[i].bg_free_inodes_count, gd[i].bg_free_inodes_count);
493
494                 // count overall free blocks
495                 sb->s_free_blocks_count += fb;
496         }
497         STORE_LE(sb->s_free_blocks_count, sb->s_free_blocks_count);
498
499         // dump filesystem skeleton structures
500 //      printf("Writing superblocks and filesystem accounting information: ");
501         for (i = 0, pos = first_data_block; i < ngroups; i++, pos += blocks_per_group) {
502                 // dump superblock and group descriptors and their backups
503                 if (has_super(i)) {
504                         // N.B. 1024 byte blocks are special
505                         PUT(((uint64_t)pos * blocksize) + ((0 == i && 0 == first_data_block) ? 1024 : 0), sb, 1024);//blocksize);
506                         PUT(((uint64_t)pos * blocksize) + blocksize, gd, gdtsz * blocksize);
507                 }
508         }
509
510         // zero boot sectors
511         memset(buf, 0, blocksize);
512         PUT(0, buf, 1024); // N.B. 1024 <= blocksize, so buf[0..1023] contains zeros
513         // zero inode tables
514         for (i = 0; i < ngroups; ++i)
515                 for (n = 0; n < itsz; ++n)
516                         PUT((uint64_t)(FETCH_LE32(gd[i].bg_inode_table) + n) * blocksize, buf, blocksize);
517
518         // prepare directory inode
519         inode = (struct ext2_inode *)buf;
520         STORE_LE(inode->i_mode, S_IFDIR | S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH);
521         STORE_LE(inode->i_mtime, timestamp);
522         STORE_LE(inode->i_atime, timestamp);
523         STORE_LE(inode->i_ctime, timestamp);
524         STORE_LE(inode->i_size, blocksize);
525         // N.B. inode->i_blocks stores the number of 512 byte data blocks. Why on Earth?!
526         STORE_LE(inode->i_blocks, blocksize / 512);
527
528         // dump root dir inode
529         STORE_LE(inode->i_links_count, 3); // "/.", "/..", "/lost+found/.." point to this inode
530         STORE_LE(inode->i_block[0], FETCH_LE32(gd[0].bg_inode_table) + itsz);
531         PUT(((uint64_t)FETCH_LE32(gd[0].bg_inode_table) * blocksize) + (EXT2_ROOT_INO-1) * sizeof(*inode), buf, sizeof(*inode));
532
533         // dump lost+found dir inode
534         STORE_LE(inode->i_links_count, 2); // both "/lost+found" and "/lost+found/." point to this inode
535         STORE_LE(inode->i_block[0], inode->i_block[0]+1); // use next block
536         PUT(((uint64_t)FETCH_LE32(gd[0].bg_inode_table) * blocksize) + (EXT2_GOOD_OLD_FIRST_INO-1) * sizeof(*inode), buf, sizeof(*inode));
537
538         // dump directories
539         memset(buf, 0, blocksize);
540         dir = (struct ext2_dir *)buf;
541
542         // dump lost+found dir block
543         STORE_LE(dir->inode1, EXT2_GOOD_OLD_FIRST_INO);
544         STORE_LE(dir->rec_len1, 12);
545         STORE_LE(dir->name_len1, 1);
546         STORE_LE(dir->file_type1, EXT2_FT_DIR);
547         dir->name1[0] = '.';
548         STORE_LE(dir->inode2, EXT2_ROOT_INO);
549         STORE_LE(dir->rec_len2, blocksize - 12);
550         STORE_LE(dir->name_len2, 2);
551         STORE_LE(dir->file_type2, EXT2_FT_DIR);
552         dir->name2[0] = '.'; dir->name2[1] = '.';
553         PUT((uint64_t)(FETCH_LE32(gd[0].bg_inode_table) + itsz + 1) * blocksize, buf, blocksize);
554
555         // dump root dir block
556         STORE_LE(dir->inode1, EXT2_ROOT_INO);
557         STORE_LE(dir->rec_len2, 12);
558         STORE_LE(dir->inode3, EXT2_GOOD_OLD_FIRST_INO);
559         STORE_LE(dir->rec_len3, blocksize - 12 - 12);
560         STORE_LE(dir->name_len3, 10);
561         STORE_LE(dir->file_type3, EXT2_FT_DIR);
562         strcpy(dir->name3, "lost+found");
563         PUT((uint64_t)(FETCH_LE32(gd[0].bg_inode_table) + itsz + 0) * blocksize, buf, blocksize);
564
565  done:
566         // cleanup
567         if (ENABLE_FEATURE_CLEAN_UP) {
568                 free(buf);
569                 free(gd);
570                 free(sb);
571         }
572
573         xclose(fd);
574         return EXIT_SUCCESS;
575 }