releasing version 0.9.5-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_sectors;
106         off_t i;
107         void* sector;
108
109         erase_size = ((uint64_t)
110                         le32_to_cpu(sb.fat_sector_start) +
111                         le32_to_cpu(sb.fat_sector_count)) * SECTOR_SIZE(sb);
112         erase_size = ROUND_UP(erase_size, cbm_alignment());
113         erase_size += cbm_size();
114         erase_size = ROUND_UP(erase_size, uct_alignment());
115         erase_size += uct_size();
116         erase_size = ROUND_UP(erase_size, rootdir_alignment());
117         erase_size += rootdir_size();
118
119         erase_sectors = erase_size / SECTOR_SIZE(sb);
120
121         if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
122         {
123                 exfat_error("seek failed");
124                 return 1;
125         }
126
127         sector = malloc(SECTOR_SIZE(sb));
128         if (sector == NULL)
129         {
130                 exfat_error("failed to allocate erase sector");
131                 return 1;
132         }
133         memset(sector, 0, SECTOR_SIZE(sb));
134
135         for (i = 0; i < erase_sectors; i++)
136         {
137                 if (write(fd, sector, SECTOR_SIZE(sb)) == -1)
138                 {
139                         free(sector);
140                         exfat_error("failed to erase sector %"PRIu64, i);
141                         return 1;
142                 }
143                 if (i * 100 / erase_sectors != (i + 1) * 100 / erase_sectors)
144                 {
145                         printf("\b\b\b%2"PRIu64"%%", (i + 1) * 100 / erase_sectors);
146                         fflush(stdout);
147                 }
148         }
149         free(sector);
150         return 0;
151 }
152
153 /*
154  * exFAT layout:
155  * - Volume Boot Record (VBR)
156  *   - Main Boot Sector (MBR)
157  *   - Main Extended Boot Sectors (MEBS)
158  *   - OEM Parameters
159  *   - Reserved sector
160  *   - Checksum sector
161  * - Volume Boot Record copy
162  * - File Allocation Table (FAT)
163  * - Clusters heap
164  *   - Clusters bitmap
165  *   - Upper case table
166  *   - Root directory
167  */
168 #define FS_OBJECT(order, name) \
169         {#name, order, name##_alignment, name##_size, name##_write}
170 static struct exfat_structure structures[] =
171 {
172         FS_OBJECT(3, vbr),
173         FS_OBJECT(3, vbr),
174         FS_OBJECT(2, fat),
175         FS_OBJECT(1, cbm),
176         FS_OBJECT(1, uct),
177         FS_OBJECT(1, rootdir)
178 };
179 #undef FS_OBJECT
180
181 static off_t write_structure(int fd, struct exfat_structure* structure,
182                 off_t current)
183 {
184         off_t alignment = structure->get_alignment();
185         off_t base = ROUND_UP(current, alignment);
186
187         if (lseek(fd, base, SEEK_SET) == (off_t) -1)
188         {
189                 exfat_error("seek to %"PRIu64" failed", base);
190                 return -1;
191         }
192         if (structure->order > 0)
193         {
194                 int rc = structure->write_data(base, fd);
195                 if (rc != 0)
196                 {
197                         exfat_error("%s creation failed: %s", structure->name,
198                                         strerror(rc));
199                         return -1;
200                 }
201                 structure->order--;
202         }
203         return base + structure->get_size();
204 }
205
206 static int write_structures(int fd)
207 {
208         off_t current;
209         size_t i;
210         int remainder;
211
212         do
213         {
214                 current = 0;
215                 remainder = 0;
216                 for (i = 0; i < sizeof(structures) / sizeof(structures[0]); i++)
217                 {
218                         current = write_structure(fd, &structures[i], current);
219                         if (current == (off_t) -1)
220                                 return 1;
221                         remainder += structures[i].order;
222                 }
223         }
224         while (remainder > 0);
225         return 0;
226 }
227
228 static int get_spc_bits(int user_defined, off_t volume_size)
229 {
230         if (user_defined != -1)
231                 return user_defined;
232
233         if (volume_size < 256ull * 1024 * 1024)
234                 return 3;       /* 4 KB */
235         else if (volume_size < 32ull * 1024 * 1024 * 1024)
236                 return 6;       /* 32 KB */
237         else
238                 return 8;       /* 128 KB */
239 }
240
241 static int set_volume_label(int fd, const char* volume_label)
242 {
243         le16_t tmp[EXFAT_ENAME_MAX + 1];
244
245         if (volume_label == NULL)
246                 return 0;
247
248         memset(tmp, 0, sizeof(tmp));
249         if (utf8_to_utf16(tmp, volume_label, EXFAT_ENAME_MAX,
250                                 strlen(volume_label)) != 0)
251         {
252                 close(fd);
253                 return 1;
254         }
255         memcpy(label_entry.name, tmp, EXFAT_ENAME_MAX * sizeof(le16_t));
256         label_entry.length = utf16_length(tmp);
257         label_entry.type |= EXFAT_ENTRY_VALID;
258         return 0;
259 }
260
261 static uint32_t get_volume_serial(uint32_t user_defined)
262 {
263         struct timeval now;
264
265         if (user_defined != 0)
266                 return user_defined;
267
268         if (gettimeofday(&now, NULL) != 0)
269                 return 0;
270         return (now.tv_sec << 20) | now.tv_usec;
271 }
272
273 static int mkfs(const char* spec, int sector_bits, int spc_bits,
274                 const char* volume_label, uint32_t volume_serial, int first_sector)
275 {
276         int fd;
277         off_t volume_size;
278         char spec_abs[PATH_MAX];
279
280         if (realpath(spec, spec_abs) == NULL)
281         {
282                 exfat_error("failed to get absolute path for `%s'", spec);
283                 return 1;
284         }
285
286         fd = exfat_open(spec_abs, 0);
287         if (fd < 0)
288                 return 1;
289
290         volume_size = lseek(fd, 0, SEEK_END);
291         if (volume_size == (off_t) -1)
292         {
293                 close(fd);
294                 exfat_error("seek failed");
295                 return 1;
296         }
297         spc_bits = get_spc_bits(spc_bits, volume_size);
298
299         if (set_volume_label(fd, volume_label) != 0)
300         {
301                 close(fd);
302                 return 1;
303         }
304
305         volume_serial = get_volume_serial(volume_serial);
306         if (volume_serial == 0)
307         {
308                 close(fd);
309                 exfat_error("failed to get current time to form volume id");
310                 return 1;
311         }
312
313         if (init_sb(volume_size, sector_bits, spc_bits, volume_serial,
314                                 first_sector) != 0)
315         {
316                 close(fd);
317                 return 1;
318         }
319
320         printf("Creating... %2u%%", 0);
321         fflush(stdout);
322         if (erase_device(fd) != 0)
323         {
324                 close(fd);
325                 return 1;
326         }
327         if (write_structures(fd) != 0)
328         {
329                 close(fd);
330                 return 1;
331         }
332         puts("\b\b\b\bdone.");
333
334         printf("Flushing... ");
335         fflush(stdout);
336         if (fsync(fd) < 0)
337         {
338                 close(fd);
339                 exfat_error("fsync failed for `%s'", spec_abs);
340                 return 1;
341         }
342         puts("done.");
343         if (close(fd) < 0)
344         {
345                 exfat_error("close failed for `%s'", spec_abs);
346                 return 1;
347         }
348         printf("File system created successfully.\n");
349         return 0;
350 }
351
352 static int logarithm2(int n)
353 {
354         int i;
355
356         for (i = 0; i < sizeof(int) * CHAR_BIT - 1; i++)
357                 if ((1 << i) == n)
358                         return i;
359         return -1;
360 }
361
362 static void usage(const char* prog)
363 {
364         fprintf(stderr, "Usage: %s [-i volume-id] [-n label] "
365                         "[-p partition-first-sector] "
366                         "[-s sectors-per-cluster] [-v] <device>\n", prog);
367         exit(1);
368 }
369
370 int main(int argc, char* argv[])
371 {
372         const char* spec = NULL;
373         char** pp;
374         int spc_bits = -1;
375         const char* volume_label = NULL;
376         uint32_t volume_serial = 0;
377         int first_sector = 0;
378
379         printf("mkexfatfs %u.%u.%u\n",
380                         EXFAT_VERSION_MAJOR, EXFAT_VERSION_MINOR, EXFAT_VERSION_PATCH);
381
382         for (pp = argv + 1; *pp; pp++)
383         {
384                 if (strcmp(*pp, "-s") == 0)
385                 {
386                         pp++;
387                         if (*pp == NULL)
388                                 usage(argv[0]);
389                         spc_bits = logarithm2(atoi(*pp));
390                         if (spc_bits < 0)
391                         {
392                                 exfat_error("invalid option value: `%s'", *pp);
393                                 return 1;
394                         }
395                 }
396                 else if (strcmp(*pp, "-n") == 0)
397                 {
398                         pp++;
399                         if (*pp == NULL)
400                                 usage(argv[0]);
401                         volume_label = *pp;
402                         /* TODO check length */
403                 }
404                 else if (strcmp(*pp, "-i") == 0)
405                 {
406                         pp++;
407                         if (*pp == NULL)
408                                 usage(argv[0]);
409                         volume_serial = strtol(*pp, NULL, 16);
410                 }
411                 else if (strcmp(*pp, "-p") == 0)
412                 {
413                         pp++;
414                         if (*pp == NULL)
415                                 usage(argv[0]);
416                         first_sector = atoi(*pp);
417                 }
418                 else if (strcmp(*pp, "-v") == 0)
419                 {
420                         puts("Copyright (C) 2010  Andrew Nayenko");
421                         return 0;
422                 }
423                 else if (spec == NULL)
424                         spec = *pp;
425                 else
426                         usage(argv[0]);
427         }
428         if (spec == NULL)
429                 usage(argv[0]);
430
431         return mkfs(spec, 9, spc_bits, volume_label, volume_serial, first_sector);
432 }