clarify in comment
[platform/upstream/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_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_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_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_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_block) % blocks_per_group;
332                 if ((1 == ngroups) && remainder && (remainder < overhead))
333                         bb_error_msg_and_die("way small device");
334                 // Standard mke2fs uses 50. Looks like a bug in our calculation
335                 // of "remainder" or "overhead" - we don't match standard mke2fs
336                 // when we transition from one group to two groups
337                 // (a bit after 8M image size), but it works for two->three groups
338                 // transition (at 16M).
339                 if (remainder && (remainder < overhead + 50)) {
340 //bb_info_msg("CHOP[%u]", remainder);
341                         nblocks -= remainder;
342                         goto retry;
343                 }
344         }
345
346         if (nblocks_full - nblocks)
347                 printf("warning: %u blocks unused\n\n", nblocks_full - nblocks);
348         printf(
349                 "Filesystem label=%s\n"
350                 "OS type: Linux\n"
351                 "Block size=%u (log=%u)\n"
352                 "Fragment size=%u (log=%u)\n"
353                 "%u inodes, %u blocks\n"
354                 "%u blocks (%u%%) reserved for the super user\n"
355                 "First data block=%u\n"
356                 "Maximum filesystem blocks=%u\n"
357                 "%u block groups\n"
358                 "%u blocks per group, %u fragments per group\n"
359                 "%u inodes per group"
360                 , label
361                 , blocksize, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE
362                 , blocksize, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE
363                 , inodes_per_group * ngroups, nblocks
364                 , nreserved, reserved_percent
365                 , first_block
366                 , gdtsz * (blocksize / sizeof(*gd)) * blocks_per_group
367                 , ngroups
368                 , blocks_per_group, blocks_per_group
369                 , inodes_per_group
370         );
371         {
372                 const char *fmt = "\nSuperblock backups stored on blocks:\n"
373                         "\t%u";
374                 pos = first_block;
375                 for (i = 1; i < ngroups; i++) {
376                         pos += blocks_per_group;
377                         if (has_super(i)) {
378                                 printf(fmt, (unsigned)pos);
379                                 fmt = ", %u";
380                         }
381                 }
382         }
383         bb_putchar('\n');
384
385         // dry run? -> we are done
386         if (opts & OPT_n)
387                 goto done;
388
389         // TODO: 3/5 refuse if mounted
390         // TODO: 4/5 compat options
391         // TODO: 1/5 sanity checks
392         // TODO: 0/5 more verbose error messages
393         // TODO: 4/5 bigendianness: recheck, wait for ARM reporters
394         // TODO: 2/5 reserved GDT: how to mark but not allocate?
395         // TODO: 3/5 dir_index?
396
397         // fill the superblock
398         sb = xzalloc(blocksize);
399         STORE_LE(sb->s_rev_level, 1); // revision 1 filesystem
400         STORE_LE(sb->s_magic, EXT2_SUPER_MAGIC);
401         STORE_LE(sb->s_inode_size, sizeof(*inode));
402         STORE_LE(sb->s_first_ino, EXT2_GOOD_OLD_FIRST_INO);
403         STORE_LE(sb->s_log_block_size, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE);
404         STORE_LE(sb->s_log_frag_size, blocksize_log2 - EXT2_MIN_BLOCK_LOG_SIZE);
405         // first 1024 bytes of the device are for boot record. If block size is 1024 bytes, then
406         // the first block is 1, otherwise 0
407         STORE_LE(sb->s_first_data_block, first_block);
408         // block and inode bitmaps occupy no more than one block, so maximum number of blocks is
409         STORE_LE(sb->s_blocks_per_group, blocks_per_group);
410         STORE_LE(sb->s_frags_per_group, blocks_per_group);
411         // blocks
412         STORE_LE(sb->s_blocks_count, nblocks);
413         // reserve blocks for superuser
414         STORE_LE(sb->s_r_blocks_count, nreserved);
415         // ninodes
416         STORE_LE(sb->s_inodes_per_group, inodes_per_group);
417         STORE_LE(sb->s_inodes_count, inodes_per_group * ngroups);
418         STORE_LE(sb->s_free_inodes_count, inodes_per_group * ngroups - EXT2_GOOD_OLD_FIRST_INO);
419         // timestamps
420         timestamp = time(NULL);
421         STORE_LE(sb->s_mkfs_time, timestamp);
422         STORE_LE(sb->s_wtime, timestamp);
423         STORE_LE(sb->s_lastcheck, timestamp);
424         // misc
425         STORE_LE(sb->s_state, 1); // TODO: what's 1?
426         STORE_LE(sb->s_creator_os, EXT2_OS_LINUX);
427         STORE_LE(sb->s_checkinterval, 24*60*60 * 180); // 180 days
428         STORE_LE(sb->s_errors, EXT2_ERRORS_DEFAULT);
429         STORE_LE(sb->s_feature_compat, EXT2_FEATURE_COMPAT_SUPP
430                 | (EXT2_FEATURE_COMPAT_RESIZE_INO * ENABLE_FEATURE_MKFS_EXT2_RESERVED_GDT)
431                 | (EXT2_FEATURE_COMPAT_DIR_INDEX * ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX)
432         );
433         // e2fsck from 1.41.9 doesn't like EXT2_FEATURE_INCOMPAT_WHITEOUT
434         STORE_LE(sb->s_feature_incompat, EXT2_FEATURE_INCOMPAT_FILETYPE);// | EXT2_FEATURE_INCOMPAT_WHITEOUT;
435         STORE_LE(sb->s_feature_ro_compat, EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER);
436         STORE_LE(sb->s_flags, EXT2_FLAGS_SIGNED_HASH * ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX);
437         generate_uuid(sb->s_uuid);
438         if (ENABLE_FEATURE_MKFS_EXT2_DIR_INDEX) {
439                 STORE_LE(sb->s_def_hash_version, EXT2_HASH_HALF_MD4);
440                 generate_uuid((uint8_t *)sb->s_hash_seed);
441         }
442         /*
443          * From e2fsprogs: add "jitter" to the superblock's check interval so that we
444          * don't check all the filesystems at the same time.  We use a
445          * kludgy hack of using the UUID to derive a random jitter value.
446          */
447         STORE_LE(sb->s_max_mnt_count, EXT2_DFL_MAX_MNT_COUNT
448                 + (sb->s_uuid[ARRAY_SIZE(sb->s_uuid)-1] % EXT2_DFL_MAX_MNT_COUNT));
449
450         // write the label
451         safe_strncpy((char *)sb->s_volume_name, label, sizeof(sb->s_volume_name));
452
453         // fill group descriptors
454         gd = xzalloc(gdtsz * blocksize);
455         buf = xmalloc(blocksize);
456         sb->s_free_blocks_count = 0;
457         for (i = 0, pos = first_block, n = nblocks - first_block;
458                 i < ngroups;
459                 i++, pos += blocks_per_group, n -= blocks_per_group
460         ) {
461                 uint32_t overhead = pos + (has_super(i) ? (1/*sb*/ + gdtsz) : 0);
462                 uint32_t fb;
463                 STORE_LE(gd[i].bg_block_bitmap, overhead + 0);
464                 STORE_LE(gd[i].bg_inode_bitmap, overhead + 1);
465                 STORE_LE(gd[i].bg_inode_table, overhead + 2);
466                 overhead = overhead - pos + 1/*bbmp*/ + 1/*ibmp*/ + itsz;
467                 gd[i].bg_free_inodes_count = inodes_per_group;
468                 //STORE_LE(gd[i].bg_used_dirs_count, 0);
469                 // N.B. both root and lost+found dirs are within the first block group, thus +2
470                 if (0 == i) {
471                         overhead += 2;
472                         STORE_LE(gd[i].bg_used_dirs_count, 2);
473                         gd[i].bg_free_inodes_count -= EXT2_GOOD_OLD_FIRST_INO;
474                 }
475
476                 // mark preallocated blocks as allocated
477                 fb = (n < blocks_per_group ? n : blocks_per_group) - overhead;
478 //bb_info_msg("ALLOC: [%u][%u][%u]", blocksize, overhead, blocks_per_group - (fb + overhead));
479                 allocate(buf, blocksize,
480                         overhead,
481                         blocks_per_group - (fb + overhead)
482                 );
483                 // dump block bitmap
484                 PUT((uint64_t)(FETCH_LE32(gd[i].bg_block_bitmap)) * blocksize, buf, blocksize);
485                 STORE_LE(gd[i].bg_free_blocks_count, fb);
486
487                 // mark preallocated inodes as allocated
488                 allocate(buf, blocksize,
489                         inodes_per_group - gd[i].bg_free_inodes_count,
490                         blocks_per_group - inodes_per_group
491                 );
492                 // dump inode bitmap
493                 //PUT((uint64_t)(FETCH_LE32(gd[i].bg_block_bitmap)) * blocksize, buf, blocksize);
494                 //but it's right after block bitmap, so we can just:
495                 xwrite(fd, buf, blocksize);
496                 STORE_LE(gd[i].bg_free_inodes_count, gd[i].bg_free_inodes_count);
497
498                 // count overall free blocks
499                 sb->s_free_blocks_count += fb;
500         }
501         STORE_LE(sb->s_free_blocks_count, sb->s_free_blocks_count);
502
503         // dump filesystem skeleton structures
504 //      printf("Writing superblocks and filesystem accounting information: ");
505         for (i = 0, pos = first_block; i < ngroups; i++, pos += blocks_per_group) {
506                 // dump superblock and group descriptors and their backups
507                 if (has_super(i)) {
508                         // N.B. 1024 byte blocks are special
509                         PUT(((uint64_t)pos * blocksize) + ((0 == i && 0 == first_block) ? 1024 : 0), sb, 1024);//blocksize);
510                         PUT(((uint64_t)pos * blocksize) + blocksize, gd, gdtsz * blocksize);
511                 }
512         }
513
514         // zero boot sectors
515         memset(buf, 0, blocksize);
516         PUT(0, buf, 1024); // N.B. 1024 <= blocksize, so buf[0..1023] contains zeros
517         // zero inode tables
518         for (i = 0; i < ngroups; ++i)
519                 for (n = 0; n < itsz; ++n)
520                         PUT((uint64_t)(FETCH_LE32(gd[i].bg_inode_table) + n) * blocksize, buf, blocksize);
521
522         // prepare directory inode
523         inode = (struct ext2_inode *)buf;
524         STORE_LE(inode->i_mode, S_IFDIR | S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH);
525         STORE_LE(inode->i_mtime, timestamp);
526         STORE_LE(inode->i_atime, timestamp);
527         STORE_LE(inode->i_ctime, timestamp);
528         STORE_LE(inode->i_size, blocksize);
529         // N.B. inode->i_blocks stores the number of 512 byte data blocks. Why on Earth?!
530         STORE_LE(inode->i_blocks, blocksize / 512);
531
532         // dump root dir inode
533         STORE_LE(inode->i_links_count, 3); // "/.", "/..", "/lost+found/.." point to this inode
534         STORE_LE(inode->i_block[0], FETCH_LE32(gd[0].bg_inode_table) + itsz);
535         PUT(((uint64_t)FETCH_LE32(gd[0].bg_inode_table) * blocksize) + (EXT2_ROOT_INO-1) * sizeof(*inode), buf, sizeof(*inode));
536
537         // dump lost+found dir inode
538         STORE_LE(inode->i_links_count, 2); // both "/lost+found" and "/lost+found/." point to this inode
539         STORE_LE(inode->i_block[0], inode->i_block[0]+1); // use next block
540         PUT(((uint64_t)FETCH_LE32(gd[0].bg_inode_table) * blocksize) + (EXT2_GOOD_OLD_FIRST_INO-1) * sizeof(*inode), buf, sizeof(*inode));
541
542         // dump directories
543         memset(buf, 0, blocksize);
544         dir = (struct ext2_dir *)buf;
545
546         // dump lost+found dir block
547         STORE_LE(dir->inode1, EXT2_GOOD_OLD_FIRST_INO);
548         STORE_LE(dir->rec_len1, 12);
549         STORE_LE(dir->name_len1, 1);
550         STORE_LE(dir->file_type1, EXT2_FT_DIR);
551         dir->name1[0] = '.';
552         STORE_LE(dir->inode2, EXT2_ROOT_INO);
553         STORE_LE(dir->rec_len2, blocksize - 12);
554         STORE_LE(dir->name_len2, 2);
555         STORE_LE(dir->file_type2, EXT2_FT_DIR);
556         dir->name2[0] = '.'; dir->name2[1] = '.';
557         PUT((uint64_t)(FETCH_LE32(gd[0].bg_inode_table) + itsz + 1) * blocksize, buf, blocksize);
558
559         // dump root dir block
560         STORE_LE(dir->inode1, EXT2_ROOT_INO);
561         STORE_LE(dir->rec_len2, 12);
562         STORE_LE(dir->inode3, EXT2_GOOD_OLD_FIRST_INO);
563         STORE_LE(dir->rec_len3, blocksize - 12 - 12);
564         STORE_LE(dir->name_len3, 10);
565         STORE_LE(dir->file_type3, EXT2_FT_DIR);
566         strcpy(dir->name3, "lost+found");
567         PUT((uint64_t)(FETCH_LE32(gd[0].bg_inode_table) + itsz + 0) * blocksize, buf, blocksize);
568
569  done:
570         // cleanup
571         if (ENABLE_FEATURE_CLEAN_UP) {
572                 free(buf);
573                 free(gd);
574                 free(sb);
575         }
576
577         xclose(fd);
578         return EXIT_SUCCESS;
579 }