data, memory: Optimize .zero > PAGE_SIZE
[nbdkit/ericb.git] / plugins / floppy / directory-lfn.c
blob10caf84b218740b98b536809faa123069f61b3a1
1 /* nbdkit
2 * Copyright (C) 2018 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 /* This file deals only with directories and long file names (LFNs).
34 * Turns out to be the most complicated part of the FAT format.
37 #include <config.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <stdint.h>
42 #include <inttypes.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <assert.h>
46 #include <iconv.h>
47 #include <time.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
51 #include <nbdkit-plugin.h>
53 #include "byte-swapping.h"
55 #include "virtual-floppy.h"
57 /* Used for dealing with VFAT LFNs when creating a directory. */
58 struct lfn {
59 const char *name; /* Original Unix filename. */
60 char short_base[8]; /* Short basename. */
61 char short_ext[3]; /* Short file extension. */
62 char *lfn; /* Long filename for MS-DOS as UTF16-LE. */
63 size_t lfn_size; /* Size *in bytes* of lfn. */
66 static int add_volume_label (const char *label, size_t di, struct virtual_floppy *floppy);
67 static int add_dot_entries (size_t di, struct virtual_floppy *floppy);
68 static int add_directory_entry (const struct lfn *lfn, uint8_t attributes, uint32_t file_size, struct stat *statbuf, size_t di, struct virtual_floppy *floppy);
69 static uint8_t lfn_checksum (const struct lfn *lfn);
70 static void set_times (const struct stat *statbuf, struct dir_entry *entry);
71 static int convert_long_file_names (struct lfn *lfns, size_t n);
72 static int convert_to_utf16le (const char *name, char **out, size_t *output_len);
73 static void free_lfns (struct lfn *lfns, size_t n);
74 static ssize_t extend_dir_table (size_t di, struct virtual_floppy *floppy);
76 /* Create the on disk directory table for dirs[di]. */
77 int
78 create_directory (size_t di, const char *label,
79 struct virtual_floppy *floppy)
81 size_t i;
82 const size_t nr_subdirs = floppy->dirs[di].nr_subdirs;
83 const size_t nr_files = floppy->dirs[di].nr_files;
84 struct lfn *lfns, *lfn;
85 const char *name;
86 uint8_t attributes;
87 uint32_t file_size;
88 struct stat *statbuf;
90 if (di == 0) {
91 /* For root directory, add the volume label entry first. */
92 if (add_volume_label (label, di, floppy) == -1)
93 return -1;
95 else {
96 /* For subdirectories, add "." and ".." entries. */
97 if (add_dot_entries (di, floppy) == -1)
98 return -1;
101 /* Convert all the filenames in the directory into short and long
102 * names. This has to be done for the whole directory because
103 * conflicting short names must be renamed.
105 lfns = calloc (nr_subdirs + nr_files, sizeof (struct lfn));
106 if (lfns == NULL) {
107 nbdkit_error ("calloc: %m");
108 return -1;
110 for (i = 0; i < nr_subdirs; ++i) {
111 const size_t sdi = floppy->dirs[di].subdirs[i];
112 assert (sdi < floppy->nr_dirs);
114 name = floppy->dirs[sdi].name;
115 lfns[i].name = name;
117 for (i = 0; i < nr_files; ++i) {
118 const size_t fi = floppy->dirs[di].files[i];
119 assert (fi < floppy->nr_files);
121 name = floppy->files[fi].name;
122 lfns[nr_subdirs+i].name = name;
125 if (convert_long_file_names (lfns, nr_subdirs + nr_files) == -1) {
126 free_lfns (lfns, nr_subdirs + nr_files);
127 return -1;
130 /* Add subdirectories. */
131 attributes = DIR_ENTRY_SUBDIRECTORY; /* Same as set by Linux kernel. */
132 file_size = 0;
133 for (i = 0; i < nr_subdirs; ++i) {
134 const size_t sdi = floppy->dirs[di].subdirs[i];
135 assert (sdi < floppy->nr_dirs);
137 lfn = &lfns[i];
138 statbuf = &floppy->dirs[sdi].statbuf;
140 if (add_directory_entry (lfn, attributes, file_size,
141 statbuf, di, floppy) == -1) {
142 free_lfns (lfns, nr_subdirs + nr_files);
143 return -1;
147 /* Add files. */
148 attributes = DIR_ENTRY_ARCHIVE; /* Same as set by Linux kernel. */
149 for (i = 0; i < nr_files; ++i) {
150 const size_t fi = floppy->dirs[di].files[i];
151 assert (fi < floppy->nr_files);
153 lfn = &lfns[nr_subdirs+i];
154 statbuf = &floppy->files[fi].statbuf;
155 file_size = statbuf->st_size;
157 if (add_directory_entry (lfn, attributes, file_size,
158 statbuf, di, floppy) == -1) {
159 free_lfns (lfns, nr_subdirs + nr_files);
160 return -1;
164 free_lfns (lfns, nr_subdirs + nr_files);
165 return 0;
168 /* Add the volume label to dirs[0].table. */
169 static int
170 add_volume_label (const char *label, size_t di, struct virtual_floppy *floppy)
172 ssize_t i;
173 struct dir_entry entry;
175 assert (di == 0);
177 memset (&entry, 0, sizeof entry);
178 pad_string (label, 11, entry.name);
179 entry.attributes = DIR_ENTRY_VOLUME_LABEL; /* Same as dosfstools. */
181 i = extend_dir_table (di, floppy);
182 if (i == -1)
183 return -1;
184 floppy->dirs[di].table[i] = entry;
185 return 0;
188 /* Add "." and ".." entries for subdirectories. */
189 static int
190 add_dot_entries (size_t di, struct virtual_floppy *floppy)
192 ssize_t i, pdi;
193 struct dir_entry entry;
195 assert (di != 0);
197 memset (&entry, 0, sizeof entry);
198 pad_string (".", 11, entry.name);
199 entry.attributes = DIR_ENTRY_SUBDIRECTORY;
200 set_times (&floppy->dirs[di].statbuf, &entry);
202 i = extend_dir_table (di, floppy);
203 if (i == -1)
204 return -1;
205 floppy->dirs[di].table[i] = entry;
207 memset (&entry, 0, sizeof entry);
208 pad_string ("..", 11, entry.name);
209 entry.attributes = DIR_ENTRY_SUBDIRECTORY;
210 pdi = floppy->dirs[di].pdi;
211 set_times (&floppy->dirs[pdi].statbuf, &entry);
213 i = extend_dir_table (di, floppy);
214 if (i == -1)
215 return -1;
216 floppy->dirs[di].table[i] = entry;
218 return 0;
221 /* Either truncate or pad a string (with spaces). */
222 void
223 pad_string (const char *label, size_t n, uint8_t *out)
225 const size_t len = strlen (label);
227 memcpy (out, label, len <= n ? len : n);
228 if (len < n)
229 memset (out+len, ' ', n-len);
232 /* Add a directory entry to dirs[di].table. */
233 static int
234 add_directory_entry (const struct lfn *lfn,
235 uint8_t attributes, uint32_t file_size,
236 struct stat *statbuf,
237 size_t di, struct virtual_floppy *floppy)
239 uint8_t seq, checksum;
240 ssize_t i;
241 size_t j;
242 struct lfn_entry lfn_entry;
243 struct dir_entry entry;
244 int last_seq = 1;
246 /* LFN support.
248 * Iterate in reverse over the sequence numbers. If the filename is:
250 * "ABCDEFGHIJKLMNO"
252 * assuming those are UCS-2 codepoints, so lfn_size = 15*2 = 30,
253 * then we generate these LFN sequences:
255 * seq byte_offset s[13]
256 * 0x42 26 "NO<--zeroes->"
257 * 0x01 0 "ABCDEFGHIJKLM"
259 checksum = lfn_checksum (lfn);
260 for (seq = 1 + lfn->lfn_size/2/13; seq >= 1; --seq) {
261 size_t byte_offset = (seq-1)*2*13, r;
262 uint16_t s[13];
264 /* Copy the portion of the LFN into s.
265 * r = Number of bytes from the original string to copy.
267 r = lfn->lfn_size - byte_offset;
268 if (r > 26)
269 memcpy (s, &lfn->lfn[byte_offset], 26);
270 else {
271 memcpy (s, &lfn->lfn[byte_offset], r);
272 /* Pad remaining filename with 0. */
273 for (j = r/2; j < 13; ++j)
274 s[j] = htole16 (0);
277 memset (&lfn_entry, 0, sizeof lfn_entry);
278 lfn_entry.seq = seq;
279 if (last_seq) {
280 lfn_entry.seq |= 0x40;
281 last_seq = 0;
283 lfn_entry.attributes = 0xf;
284 lfn_entry.checksum = checksum;
286 /* Copy the name portion to the fields in the LFN entry. */
287 memcpy (lfn_entry.name1, &s[0], 5*2);
288 memcpy (lfn_entry.name2, &s[5], 6*2);
289 memcpy (lfn_entry.name3, &s[11], 2*2);
291 i = extend_dir_table (di, floppy);
292 if (i == -1)
293 return -1;
294 memcpy (&floppy->dirs[di].table[i], &lfn_entry, sizeof (struct dir_entry));
297 /* Create the 8.3 (short name / DOS-compatible) entry. */
298 memset (&entry, 0, sizeof entry);
299 memcpy (entry.name, lfn->short_base, 8);
300 memcpy (entry.name+8, lfn->short_ext, 3);
301 entry.attributes = attributes;
302 set_times (statbuf, &entry);
303 entry.size = htole32 (file_size);
304 /* Note that entry.cluster_hi and .cluster_lo are set later on in
305 * update_directory_first_cluster.
308 i = extend_dir_table (di, floppy);
309 if (i == -1)
310 return -1;
311 floppy->dirs[di].table[i] = entry;
313 return 0;
316 /* Compute a checksum for the shortname. In writable LFN filesystems
317 * this is used to check whether a non-LFN-aware operating system
318 * (ie. MS-DOS) has edited the directory. It would ignore the hidden
319 * LFN entries and so the checksum would be wrong.
321 static uint8_t
322 lfn_checksum (const struct lfn *lfn)
324 uint8_t checksum;
325 size_t i;
327 checksum = 0;
328 for (i = 0; i < 8; ++i)
329 checksum = ((checksum & 1) << 7) + (checksum >> 1) + lfn->short_base[i];
330 for (i = 0; i < 3; ++i)
331 checksum = ((checksum & 1) << 7) + (checksum >> 1) + lfn->short_ext[i];
333 return checksum;
336 /* Set the {c,m,a}date and {c,m}time fields in the entry structure
337 * based on metadata found in the statbuf.
339 #define MAKE_ENTRY_TIME(tm) \
340 ((tm).tm_hour << 11 | (tm).tm_min << 5 | ((tm).tm_sec / 2))
341 #define MAKE_ENTRY_DATE(tm) \
342 (((tm).tm_year - 80) << 9 | ((tm).tm_mon + 1) << 5 | (tm).tm_mday)
344 static void
345 set_times (const struct stat *statbuf, struct dir_entry *entry)
347 struct tm ctime_tm, mtime_tm, atime_tm;
349 localtime_r (&statbuf->st_ctime, &ctime_tm);
350 entry->ctime = MAKE_ENTRY_TIME (ctime_tm);
351 entry->ctime_10ms = 100 * (ctime_tm.tm_sec % 2);
352 entry->cdate = MAKE_ENTRY_DATE (ctime_tm);
354 localtime_r (&statbuf->st_mtime, &mtime_tm);
355 entry->mtime = MAKE_ENTRY_TIME (mtime_tm);
356 entry->mdate = MAKE_ENTRY_DATE (mtime_tm);
358 localtime_r (&statbuf->st_atime, &atime_tm);
359 entry->adate = MAKE_ENTRY_DATE (atime_tm);
362 #define UPPER_ASCII "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
363 #define LOWER_ASCII "abcdefghijklmnopqrstuvwxyz"
364 #define NUMBERS_ASCII "0123456789"
366 /* Characters which are valid in short names.
368 * Lowercase is not actually valid, but it makes the
369 * implementation below simpler and we toupper the final string.
371 * ~ is also valid but don't include it here because we want to
372 * keep it as a special character for renaming duplicates below.
374 static const char short_name_ok[] =
375 UPPER_ASCII LOWER_ASCII NUMBERS_ASCII "!#$%&'()-@^_`{}";
377 static int
378 convert_long_file_names (struct lfn *lfns, size_t n)
380 size_t i, j, len;
381 struct lfn *lfn;
383 /* Split the filenames to generate a list of short basenames + extensions. */
384 for (i = 0; i < n; ++i) {
385 const char *p;
387 lfn = &lfns[i];
389 len = strspn (lfn->name, short_name_ok);
390 memcpy (lfns[i].short_base, lfn->name, len <= 8 ? len : 8);
391 if (len < 8)
392 memset (lfn->short_base+len, ' ', 8-len);
393 /* Look for the extension. */
394 p = strrchr (lfn->name, '.');
395 if (p) {
396 p++;
397 len = strspn (p, short_name_ok);
398 memcpy (lfn->short_ext, p, len <= 3 ? len : 3);
399 if (len < 3)
400 memset (lfn->short_ext+len, ' ', 3-len);
402 else
403 memset (lfn->short_ext, ' ', sizeof lfn->short_ext);
405 /* Convert short name to upper case (ASCII only). */
406 for (j = 0; j < 8; ++j) {
407 if (strchr (LOWER_ASCII, lfn->short_base[j]))
408 lfn->short_base[j] -= 32;
410 for (j = 0; j < 3; ++j) {
411 if (strchr (LOWER_ASCII, lfn->short_ext[j]))
412 lfn->short_ext[j] -= 32;
415 /* Convert the original filename to UTF16-LE. Maximum LFN length
416 * is 0x3f * 13 = 819 UCS-2 characters.
418 if (convert_to_utf16le (lfn->name, &lfn->lfn, &lfn->lfn_size) == -1)
419 return -1;
420 if (lfn->lfn_size > 2*819) {
421 nbdkit_error ("%s: filename is too long", lfn->name);
422 return -1;
426 /* Now we must see if some short filenames are duplicates and
427 * rename them. XXX Unfortunately O(n^2).
429 for (i = 1; i < n; ++i) {
430 for (j = 0; j < i; ++j) {
431 if (memcmp (lfns[i].short_base, lfns[j].short_base, 8) == 0 &&
432 memcmp (lfns[i].short_ext, lfns[j].short_ext, 3) == 0) {
433 char s[9];
434 ssize_t k;
436 /* Entry i is a duplicate of j (j < i). So we will rename i. */
437 lfn = &lfns[i];
439 len = snprintf (s, sizeof s, "~%zu", i);
440 assert (len >= 2 && len <= 8);
442 k = 8-len;
443 while (k > 0 && lfn->short_base[k] == ' ')
444 k--;
445 memcpy (&lfn->short_base[k], s, len);
450 return 0;
453 static const char lfn_encoding[] =
454 "UTF-16LE"
455 #ifdef __GNU_LIBRARY__
456 "//TRANSLIT"
457 #endif
460 static int
461 convert_to_utf16le (const char *name, char **out, size_t *output_len)
463 iconv_t ic;
464 const size_t input_len = strlen (name);
465 size_t outalloc, inlen, outlen, prev, r;
466 const char *inp;
467 char *outp;
469 /* XXX Assumes current locale is UTF-8. */
470 ic = iconv_open (lfn_encoding, "UTF-8");
471 if (ic == (iconv_t)-1) {
472 nbdkit_error ("iconv: %m");
473 return -1;
475 outalloc = input_len;
477 again:
478 inlen = input_len;
479 outlen = outalloc;
480 *out = malloc (outlen + 1);
481 if (*out == NULL) {
482 nbdkit_error ("malloc: %m");
483 iconv_close (ic);
484 return -1;
486 inp = name;
487 outp = *out;
489 r = iconv (ic, (char **) &inp, &inlen, &outp, &outlen);
490 if (r == (size_t)-1) {
491 if (errno == E2BIG) {
492 prev = outalloc;
493 /* Try again with a larger buffer. */
494 free (*out);
495 *out = NULL;
496 outalloc *= 2;
497 if (outalloc < prev) {
498 nbdkit_error ("iconv: %m");
499 iconv_close (ic);
500 return -1;
502 /* Erase errno so we don't return it to the caller by accident. */
503 errno = 0;
504 goto again;
506 else {
507 /* EILSEQ etc. */
508 nbdkit_error ("iconv: %s: %m", name);
509 free (*out);
510 *out = NULL;
511 iconv_close (ic);
512 return -1;
515 *outp = '\0';
516 iconv_close (ic);
517 if (output_len != NULL)
518 *output_len = outp - *out;
520 return 0;
523 static void
524 free_lfns (struct lfn *lfns, size_t n)
526 size_t i;
528 for (i = 0; i < n; ++i)
529 free (lfns[i].lfn);
530 free (lfns);
533 /* Extend dirs[di].table by 1 directory entry. */
534 static ssize_t
535 extend_dir_table (size_t di, struct virtual_floppy *floppy)
537 struct dir_entry *p;
538 size_t i;
540 i = floppy->dirs[di].table_entries;
541 p = realloc (floppy->dirs[di].table, sizeof (struct dir_entry) * (i+1));
542 if (p == NULL) {
543 nbdkit_error ("realloc: %m");
544 return -1;
546 floppy->dirs[di].table = p;
547 floppy->dirs[di].table_entries++;
548 memset (&floppy->dirs[di].table[i], 0, sizeof (struct dir_entry));
549 return i;
552 /* In create_directory / add_directory_entry above we run before we
553 * have finalised the .first_cluster fields (because that cannot be
554 * done until we have sized all the directories). Here we fix the
555 * directory entries with the final cluster number. Note we must only
556 * touch plain directory entries (not the volume label or LFN).
559 update_directory_first_cluster (size_t di, struct virtual_floppy *floppy)
561 size_t i, j, pdi;
562 const size_t nr_subdirs = floppy->dirs[di].nr_subdirs;
563 const size_t nr_files = floppy->dirs[di].nr_files;
564 uint32_t first_cluster;
565 struct dir_entry *entry;
567 /* NB: This function makes assumptions about the order in which
568 * subdirectories and files are added to the table so that we can
569 * avoid having to maintain another mapping from subdirs/files to
570 * table entries.
572 i = 0;
573 for (j = 0; j < floppy->dirs[di].table_entries; ++j) {
574 entry = &floppy->dirs[di].table[j];
576 /* Skip LFN entries. */
577 if (entry->attributes == 0xf)
578 continue; /* don't increment i */
580 /* Skip the volume label in the root directory. */
581 if (entry->attributes == DIR_ENTRY_VOLUME_LABEL)
582 continue; /* don't increment i */
584 /* Set the first cluster of the "." entry to point to self. */
585 if (entry->attributes == DIR_ENTRY_SUBDIRECTORY &&
586 memcmp (entry->name, ". ", 11) == 0) {
587 first_cluster = floppy->dirs[di].first_cluster;
588 entry->cluster_hi = htole16 (first_cluster >> 16);
589 entry->cluster_lo = htole16 (first_cluster & 0xffff);
590 continue; /* don't increment i */
593 /* Set the first cluster of the ".." entry to point to parent. */
594 if (entry->attributes == DIR_ENTRY_SUBDIRECTORY &&
595 memcmp (entry->name, ".. ", 11) == 0) {
596 pdi = floppy->dirs[di].pdi;
597 first_cluster = floppy->dirs[pdi].first_cluster;
598 entry->cluster_hi = htole16 (first_cluster >> 16);
599 entry->cluster_lo = htole16 (first_cluster & 0xffff);
600 continue; /* don't increment i */
603 /* Otherwise it's a short name entry so we must now update the
604 * first cluster.
606 if (i < nr_subdirs) {
607 const size_t sdi = floppy->dirs[di].subdirs[i];
608 assert (sdi < floppy->nr_dirs);
609 first_cluster = floppy->dirs[sdi].first_cluster;
611 else if (i < nr_subdirs + nr_files) {
612 const size_t fi = floppy->dirs[di].files[i-nr_subdirs];
613 assert (fi < floppy->nr_files);
614 first_cluster = floppy->files[fi].first_cluster;
616 else
617 abort ();
619 entry->cluster_hi = htole16 (first_cluster >> 16);
620 entry->cluster_lo = htole16 (first_cluster & 0xffff);
621 ++i;
624 return 0;