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
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
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.
48 #include <sys/types.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. */
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]. */
78 create_directory (size_t di
, const char *label
,
79 struct virtual_floppy
*floppy
)
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
;
91 /* For root directory, add the volume label entry first. */
92 if (add_volume_label (label
, di
, floppy
) == -1)
96 /* For subdirectories, add "." and ".." entries. */
97 if (add_dot_entries (di
, floppy
) == -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
));
107 nbdkit_error ("calloc: %m");
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
;
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
);
130 /* Add subdirectories. */
131 attributes
= DIR_ENTRY_SUBDIRECTORY
; /* Same as set by Linux kernel. */
133 for (i
= 0; i
< nr_subdirs
; ++i
) {
134 const size_t sdi
= floppy
->dirs
[di
].subdirs
[i
];
135 assert (sdi
< floppy
->nr_dirs
);
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
);
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
);
164 free_lfns (lfns
, nr_subdirs
+ nr_files
);
168 /* Add the volume label to dirs[0].table. */
170 add_volume_label (const char *label
, size_t di
, struct virtual_floppy
*floppy
)
173 struct dir_entry entry
;
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
);
184 floppy
->dirs
[di
].table
[i
] = entry
;
188 /* Add "." and ".." entries for subdirectories. */
190 add_dot_entries (size_t di
, struct virtual_floppy
*floppy
)
193 struct dir_entry entry
;
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
);
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
);
216 floppy
->dirs
[di
].table
[i
] = entry
;
221 /* Either truncate or pad a string (with spaces). */
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
);
229 memset (out
+len
, ' ', n
-len
);
232 /* Add a directory entry to dirs[di].table. */
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
;
242 struct lfn_entry lfn_entry
;
243 struct dir_entry entry
;
248 * Iterate in reverse over the sequence numbers. If the filename is:
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
;
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
;
269 memcpy (s
, &lfn
->lfn
[byte_offset
], 26);
271 memcpy (s
, &lfn
->lfn
[byte_offset
], r
);
272 /* Pad remaining filename with 0. */
273 for (j
= r
/2; j
< 13; ++j
)
277 memset (&lfn_entry
, 0, sizeof lfn_entry
);
280 lfn_entry
.seq
|= 0x40;
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
);
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
);
311 floppy
->dirs
[di
].table
[i
] = entry
;
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.
322 lfn_checksum (const struct lfn
*lfn
)
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
];
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)
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
"!#$%&'()-@^_`{}";
378 convert_long_file_names (struct lfn
*lfns
, size_t n
)
383 /* Split the filenames to generate a list of short basenames + extensions. */
384 for (i
= 0; i
< n
; ++i
) {
389 len
= strspn (lfn
->name
, short_name_ok
);
390 memcpy (lfns
[i
].short_base
, lfn
->name
, len
<= 8 ? len
: 8);
392 memset (lfn
->short_base
+len
, ' ', 8-len
);
393 /* Look for the extension. */
394 p
= strrchr (lfn
->name
, '.');
397 len
= strspn (p
, short_name_ok
);
398 memcpy (lfn
->short_ext
, p
, len
<= 3 ? len
: 3);
400 memset (lfn
->short_ext
+len
, ' ', 3-len
);
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)
420 if (lfn
->lfn_size
> 2*819) {
421 nbdkit_error ("%s: filename is too long", lfn
->name
);
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) {
436 /* Entry i is a duplicate of j (j < i). So we will rename i. */
439 len
= snprintf (s
, sizeof s
, "~%zu", i
);
440 assert (len
>= 2 && len
<= 8);
443 while (k
> 0 && lfn
->short_base
[k
] == ' ')
445 memcpy (&lfn
->short_base
[k
], s
, len
);
453 static const char lfn_encoding
[] =
455 #ifdef __GNU_LIBRARY__
461 convert_to_utf16le (const char *name
, char **out
, size_t *output_len
)
464 const size_t input_len
= strlen (name
);
465 size_t outalloc
, inlen
, outlen
, prev
, r
;
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");
475 outalloc
= input_len
;
480 *out
= malloc (outlen
+ 1);
482 nbdkit_error ("malloc: %m");
489 r
= iconv (ic
, (char **) &inp
, &inlen
, &outp
, &outlen
);
490 if (r
== (size_t)-1) {
491 if (errno
== E2BIG
) {
493 /* Try again with a larger buffer. */
497 if (outalloc
< prev
) {
498 nbdkit_error ("iconv: %m");
502 /* Erase errno so we don't return it to the caller by accident. */
508 nbdkit_error ("iconv: %s: %m", name
);
517 if (output_len
!= NULL
)
518 *output_len
= outp
- *out
;
524 free_lfns (struct lfn
*lfns
, size_t n
)
528 for (i
= 0; i
< n
; ++i
)
533 /* Extend dirs[di].table by 1 directory entry. */
535 extend_dir_table (size_t di
, struct virtual_floppy
*floppy
)
540 i
= floppy
->dirs
[di
].table_entries
;
541 p
= realloc (floppy
->dirs
[di
].table
, sizeof (struct dir_entry
) * (i
+1));
543 nbdkit_error ("realloc: %m");
546 floppy
->dirs
[di
].table
= p
;
547 floppy
->dirs
[di
].table_entries
++;
548 memset (&floppy
->dirs
[di
].table
[i
], 0, sizeof (struct dir_entry
));
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
)
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
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
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
;
619 entry
->cluster_hi
= htole16 (first_cluster
>> 16);
620 entry
->cluster_lo
= htole16 (first_cluster
& 0xffff);