releasing version 0.9.6-1
[sven/exfat-utils.git] / mkfs / main.c
1 /*
2         main.c (15.08.10)
3         Creates exFAT file system.
4
5         Copyright (C) 2010  Andrew Nayenko
6
7         This program is free software: you can redistribute it and/or modify
8         it under the terms of the GNU General Public License as published by
9         the Free Software Foundation, either version 3 of the License, or
10         (at your option) any later version.
11
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15         GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <sys/types.h>
22 #include <sys/time.h>
23 #include <unistd.h>
24 #include <inttypes.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <errno.h>
29 #include <exfat.h>
30 #include "vbr.h"
31 #include "fat.h"
32 #include "cbm.h"
33 #include "uct.h"
34 #include "rootdir.h"
35
36 #define ROUND_UP(x, d) (DIV_ROUND_UP(x, d) * (d))
37
38 struct exfat_super_block sb;
39 struct exfat_entry_label label_entry = {EXFAT_ENTRY_LABEL ^ EXFAT_ENTRY_VALID};
40 struct exfat_entry_bitmap bitmap_entry = {EXFAT_ENTRY_BITMAP};
41 struct exfat_entry_upcase upcase_entry = {EXFAT_ENTRY_UPCASE};
42
43 struct exfat_structure
44 {
45         const char* name;
46         int order;
47         off_t (*get_alignment)(void);
48         off_t (*get_size)(void);
49         int (*write_data)(off_t, int);
50 };
51
52 static int init_sb(off_t volume_size, int sector_bits, int spc_bits,
53                 uint32_t volume_serial, int first_sector)
54 {
55         uint32_t clusters_max = (volume_size >> sector_bits >> spc_bits);
56         uint32_t fat_sectors = DIV_ROUND_UP(clusters_max * 4, 1 << sector_bits);
57         uint32_t allocated_clusters;
58
59         memset(&sb, 0, sizeof(struct exfat_super_block));
60         sb.jump[0] = 0xeb;
61         sb.jump[1] = 0x76;
62         sb.jump[2] = 0x90;
63         memcpy(sb.oem_name, "EXFAT   ", sizeof(sb.oem_name));
64         sb.sector_start = cpu_to_le64(first_sector);
65         sb.sector_count = cpu_to_le64(volume_size >> sector_bits);
66         sb.fat_sector_start = cpu_to_le32(128); /* FIXME */
67         sb.fat_sector_count = cpu_to_le32(ROUND_UP(
68                         le32_to_cpu(sb.fat_sector_start) + fat_sectors, 1 << spc_bits) -
69                         le32_to_cpu(sb.fat_sector_start));
70         /* cluster_sector_start will be set later */
71         sb.cluster_count = cpu_to_le32(clusters_max -
72                         ((le32_to_cpu(sb.fat_sector_start) +
73                           le32_to_cpu(sb.fat_sector_count)) >> spc_bits));
74         /* rootdir_cluster will be set later */
75         sb.volume_serial = cpu_to_le32(volume_serial);
76         sb.version.major = 1;
77         sb.version.minor = 0;
78         sb.volume_state = cpu_to_le16(0);
79         sb.sector_bits = sector_bits;
80         sb.spc_bits = spc_bits;
81         sb.fat_count = 1;
82         sb.drive_no = 0x80;
83         sb.allocated_percent = 0;
84         sb.boot_signature = cpu_to_le16(0xaa55);
85
86         allocated_clusters =
87                         DIV_ROUND_UP(cbm_size(), CLUSTER_SIZE(sb)) +
88                         DIV_ROUND_UP(uct_size(), CLUSTER_SIZE(sb)) +
89                         DIV_ROUND_UP(rootdir_size(), CLUSTER_SIZE(sb));
90         if (clusters_max < ((le32_to_cpu(sb.fat_sector_start) +
91                         le32_to_cpu(sb.fat_sector_count)) >> spc_bits) +
92                         allocated_clusters)
93         {
94                 exfat_error("too small volume (%"PRIu64" bytes)", volume_size);
95                 return 1;
96         }
97         exfat_print_info(&sb, le32_to_cpu(sb.cluster_count) -
98                         allocated_clusters);
99         return 0;
100 }
101
102 static int erase_device(int fd)
103 {
104         off_t erase_size;
105         off_t erase_blocks;
106         long block_size;
107         void* block;
108         off_t i;
109
110         block_size = sysconf(_SC_PAGESIZE);
111         if (block_size < 1)
112                 block_size = 0x1000;
113
114         erase_size = ((uint64_t)
115                         le32_to_cpu(sb.fat_sector_start) +
116                         le32_to_cpu(sb.fat_sector_count)) * SECTOR_SIZE(sb);
117         erase_size = ROUND_UP(erase_size, cbm_alignment());
118         erase_size += cbm_size();
119         erase_size = ROUND_UP(erase_size, uct_alignment());
120         erase_size += uct_size();
121         erase_size = ROUND_UP(erase_size, rootdir_alignment());
122         erase_size += rootdir_size();
123
124         erase_blocks = DIV_ROUND_UP(erase_size, block_size);
125
126         if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
127         {
128                 exfat_error("seek failed");
129                 return 1;
130         }
131
132         block = malloc(block_size);
133         if (block == NULL)
134         {
135                 exfat_error("failed to allocate erase block");
136                 return 1;
137         }
138         memset(block, 0, block_size);
139
140         for (i = 0; i < erase_blocks; i++)
141         {
142                 if (write(fd, block, block_size) == -1)
143                 {
144                         free(block);
145                         exfat_error("failed to erase block %"PRIu64, i);
146                         return 1;
147                 }
148                 if (i * 100 / erase_blocks != (i + 1) * 100 / erase_blocks)
149                 {
150                         printf("\b\b\b%2"PRIu64"%%", (i + 1) * 100 / erase_blocks);
151                         fflush(stdout);
152                 }
153         }
154         free(block);
155         return 0;
156 }
157
158 /*
159  * exFAT layout:
160  * - Volume Boot Record (VBR)
161  *   - Main Boot Sector (MBR)
162  *   - Main Extended Boot Sectors (MEBS)
163  *   - OEM Parameters
164  *   - Reserved sector
165  *   - Checksum sector
166  * - Volume Boot Record copy
167  * - File Allocation Table (FAT)
168  * - Clusters heap
169  *   - Clusters bitmap
170  *   - Upper case table
171  *   - Root directory
172  */
173 #define FS_OBJECT(order, name) \
174         {#name, order, name##_alignment, name##_size, name##_write}
175 static struct exfat_structure structures[] =
176 {
177         FS_OBJECT(3, vbr),
178         FS_OBJECT(3, vbr),
179         FS_OBJECT(2, fat),
180         FS_OBJECT(1, cbm),
181         FS_OBJECT(1, uct),
182         FS_OBJECT(1, rootdir)
183 };
184 #undef FS_OBJECT
185
186 static off_t write_structure(int fd, struct exfat_structure* structure,
187                 off_t current)
188 {
189         off_t alignment = structure->get_alignment();
190         off_t base = ROUND_UP(current, alignment);
191
192         if (lseek(fd, base, SEEK_SET) == (off_t) -1)
193         {
194                 exfat_error("seek to %"PRIu64" failed", base);
195                 return -1;
196         }
197         if (structure->order > 0)
198         {
199                 int rc = structure->write_data(base, fd);
200                 if (rc != 0)
201                 {
202                         exfat_error("%s creation failed: %s", structure->name,
203                                         strerror(rc));
204                         return -1;
205                 }
206                 structure->order--;
207         }
208         return base + structure->get_size();
209 }
210
211 static int write_structures(int fd)
212 {
213         off_t current;
214         size_t i;
215         int remainder;
216
217         do
218         {
219                 current = 0;
220                 remainder = 0;
221                 for (i = 0; i < sizeof(structures) / sizeof(structures[0]); i++)
222                 {
223                         current = write_structure(fd, &structures[i], current);
224                         if (current == (off_t) -1)
225                                 return 1;
226                         remainder += structures[i].order;
227                 }
228         }
229         while (remainder > 0);
230         return 0;
231 }
232
233 static int get_spc_bits(int user_defined, off_t volume_size)
234 {
235         if (user_defined != -1)
236                 return user_defined;
237
238         if (volume_size < 256ull * 1024 * 1024)
239                 return 3;       /* 4 KB */
240         else if (volume_size < 32ull * 1024 * 1024 * 1024)
241                 return 6;       /* 32 KB */
242         else
243                 return 8;       /* 128 KB */
244 }
245
246 static int set_volume_label(int fd, const char* volume_label)
247 {
248         le16_t tmp[EXFAT_ENAME_MAX + 1];
249
250         if (volume_label == NULL)
251                 return 0;
252
253         memset(tmp, 0, sizeof(tmp));
254         if (utf8_to_utf16(tmp, volume_label, EXFAT_ENAME_MAX,
255                                 strlen(volume_label)) != 0)
256         {
257                 close(fd);
258                 return 1;
259         }
260         memcpy(label_entry.name, tmp, EXFAT_ENAME_MAX * sizeof(le16_t));
261         label_entry.length = utf16_length(tmp);
262         label_entry.type |= EXFAT_ENTRY_VALID;
263         return 0;
264 }
265
266 static uint32_t get_volume_serial(uint32_t user_defined)
267 {
268         struct timeval now;
269
270         if (user_defined != 0)
271                 return user_defined;
272
273         if (gettimeofday(&now, NULL) != 0)
274                 return 0;
275         return (now.tv_sec << 20) | now.tv_usec;
276 }
277
278 static int mkfs(const char* spec, int sector_bits, int spc_bits,
279                 const char* volume_label, uint32_t volume_serial, int first_sector)
280 {
281         int fd;
282         off_t volume_size;
283
284         fd = exfat_open(spec, 0);
285         if (fd < 0)
286                 return 1;
287
288         volume_size = lseek(fd, 0, SEEK_END);
289         if (volume_size == (off_t) -1)
290         {
291                 close(fd);
292                 exfat_error("seek failed");
293                 return 1;
294         }
295         spc_bits = get_spc_bits(spc_bits, volume_size);
296
297         if (set_volume_label(fd, volume_label) != 0)
298         {
299                 close(fd);
300                 return 1;
301         }
302
303         volume_serial = get_volume_serial(volume_serial);
304         if (volume_serial == 0)
305         {
306                 close(fd);
307                 exfat_error("failed to get current time to form volume id");
308                 return 1;
309         }
310
311         if (init_sb(volume_size, sector_bits, spc_bits, volume_serial,
312                                 first_sector) != 0)
313         {
314                 close(fd);
315                 return 1;
316         }
317
318         printf("Creating... %2u%%", 0);
319         fflush(stdout);
320         if (erase_device(fd) != 0)
321         {
322                 close(fd);
323                 return 1;
324         }
325         if (write_structures(fd) != 0)
326         {
327                 close(fd);
328                 return 1;
329         }
330         puts("\b\b\b\bdone.");
331
332         printf("Flushing... ");
333         fflush(stdout);
334         if (fsync(fd) < 0)
335         {
336                 close(fd);
337                 exfat_error("fsync failed");
338                 return 1;
339         }
340         puts("done.");
341         if (close(fd) < 0)
342         {
343                 exfat_error("close failed");
344                 return 1;
345         }
346         printf("File system created successfully.\n");
347         return 0;
348 }
349
350 static int logarithm2(int n)
351 {
352         int i;
353
354         for (i = 0; i < sizeof(int) * CHAR_BIT - 1; i++)
355                 if ((1 << i) == n)
356                         return i;
357         return -1;
358 }
359
360 static void usage(const char* prog)
361 {
362         fprintf(stderr, "Usage: %s [-i volume-id] [-n label] "
363                         "[-p partition-first-sector] "
364                         "[-s sectors-per-cluster] [-v] <device>\n", prog);
365         exit(1);
366 }
367
368 int main(int argc, char* argv[])
369 {
370         const char* spec = NULL;
371         char** pp;
372         int spc_bits = -1;
373         const char* volume_label = NULL;
374         uint32_t volume_serial = 0;
375         int first_sector = 0;
376
377         printf("mkexfatfs %u.%u.%u\n",
378                         EXFAT_VERSION_MAJOR, EXFAT_VERSION_MINOR, EXFAT_VERSION_PATCH);
379
380         for (pp = argv + 1; *pp; pp++)
381         {
382                 if (strcmp(*pp, "-s") == 0)
383                 {
384                         pp++;
385                         if (*pp == NULL)
386                                 usage(argv[0]);
387                         spc_bits = logarithm2(atoi(*pp));
388                         if (spc_bits < 0)
389                         {
390                                 exfat_error("invalid option value: `%s'", *pp);
391                                 return 1;
392                         }
393                 }
394                 else if (strcmp(*pp, "-n") == 0)
395                 {
396                         pp++;
397                         if (*pp == NULL)
398                                 usage(argv[0]);
399                         volume_label = *pp;
400                         /* TODO check length */
401                 }
402                 else if (strcmp(*pp, "-i") == 0)
403                 {
404                         pp++;
405                         if (*pp == NULL)
406                                 usage(argv[0]);
407                         volume_serial = strtol(*pp, NULL, 16);
408                 }
409                 else if (strcmp(*pp, "-p") == 0)
410                 {
411                         pp++;
412                         if (*pp == NULL)
413                                 usage(argv[0]);
414                         first_sector = atoi(*pp);
415                 }
416                 else if (strcmp(*pp, "-v") == 0)
417                 {
418                         puts("Copyright (C) 2010  Andrew Nayenko");
419                         return 0;
420                 }
421                 else if (spec == NULL)
422                         spec = *pp;
423                 else
424                         usage(argv[0]);
425         }
426         if (spec == NULL)
427                 usage(argv[0]);
428
429         return mkfs(spec, 9, spc_bits, volume_label, volume_serial, first_sector);
430 }