+1.2.5 (2016-12-05)
+* Added an option for dumpexfat to show file fragments [Daniel Drake].
+* Fixed crash when directory starts with an invalid cluster.
+* Daylight saving time in now properly reflected in file timestamps.
+
1.2.4 (2016-06-03)
* Fixed wrong files names hashes when upper case table is compressed.
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for Free exFAT implementation 1.2.4.
+# Generated by GNU Autoconf 2.69 for Free exFAT implementation 1.2.5.
#
# Report bugs to <relan@users.noreply.github.com>.
#
# Identity of this package.
PACKAGE_NAME='Free exFAT implementation'
PACKAGE_TARNAME='exfat-utils'
-PACKAGE_VERSION='1.2.4'
-PACKAGE_STRING='Free exFAT implementation 1.2.4'
+PACKAGE_VERSION='1.2.5'
+PACKAGE_STRING='Free exFAT implementation 1.2.5'
PACKAGE_BUGREPORT='relan@users.noreply.github.com'
PACKAGE_URL='https://github.com/relan/exfat'
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures Free exFAT implementation 1.2.4 to adapt to many kinds of systems.
+\`configure' configures Free exFAT implementation 1.2.5 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of Free exFAT implementation 1.2.4:";;
+ short | recursive ) echo "Configuration of Free exFAT implementation 1.2.5:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-Free exFAT implementation configure 1.2.4
+Free exFAT implementation configure 1.2.5
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by Free exFAT implementation $as_me 1.2.4, which was
+It was created by Free exFAT implementation $as_me 1.2.5, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
# Define the identity of the package.
PACKAGE='exfat-utils'
- VERSION='1.2.4'
+ VERSION='1.2.5'
cat >>confdefs.h <<_ACEOF
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by Free exFAT implementation $as_me 1.2.4, which was
+This file was extended by Free exFAT implementation $as_me 1.2.5, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-Free exFAT implementation config.status 1.2.4
+Free exFAT implementation config.status 1.2.5
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
#
AC_INIT([Free exFAT implementation],
- [1.2.4],
+ [1.2.5],
[relan@users.noreply.github.com],
[exfat-utils],
[https://github.com/relan/exfat])
.B \-u
]
[
+.B \-f
+.I file
+]
+[
.B \-V
]
.I device
Dump ranges of used sectors starting from 0 and separated with spaces. May be
useful for backup tools.
.TP
+.B \-f file
+Print out a list of fragments that compose the given file. Each fragment is
+printed on its own line, as the start offset (in bytes) into the file system,
+and the length (in bytes).
+.TP
.BI \-V
Print version and copyright.
return 0;
}
+static int dump_file_fragments(const char* spec, const char* path)
+{
+ struct exfat ef;
+ struct exfat_node* node;
+ cluster_t cluster;
+ cluster_t next_cluster;
+ cluster_t fragment_start_cluster;
+ off_t remainder;
+ off_t fragment_size = 0;
+ int rc = 0;
+
+ if (exfat_mount(&ef, spec, "ro") != 0)
+ return 1;
+
+ rc = exfat_lookup(&ef, &node, path);
+ if (rc != 0)
+ {
+ exfat_unmount(&ef);
+ exfat_error("'%s': %s", path, strerror(-rc));
+ return 1;
+ }
+
+ cluster = fragment_start_cluster = node->start_cluster;
+ remainder = node->size;
+ while (remainder > 0)
+ {
+ off_t lsize;
+
+ if (CLUSTER_INVALID(cluster))
+ {
+ exfat_error("'%s' has invalid cluster %#x", path, cluster);
+ rc = 1;
+ break;
+ }
+
+ lsize = MIN(CLUSTER_SIZE(*ef.sb), remainder);
+ fragment_size += lsize;
+ remainder -= lsize;
+
+ next_cluster = exfat_next_cluster(&ef, node, cluster);
+ if (next_cluster != cluster + 1 || remainder == 0)
+ {
+ /* next cluster is not contiguous or this is EOF */
+ printf("%"PRIu64" %"PRIu64"\n",
+ exfat_c2o(&ef, fragment_start_cluster), fragment_size);
+ /* start a new fragment */
+ fragment_start_cluster = next_cluster;
+ fragment_size = 0;
+ }
+ cluster = next_cluster;
+ }
+
+ exfat_put_node(&ef, node);
+ exfat_unmount(&ef);
+ return rc;
+}
+
static void usage(const char* prog)
{
- fprintf(stderr, "Usage: %s [-s] [-u] [-V] <device>\n", prog);
+ fprintf(stderr, "Usage: %s [-s] [-u] [-f file] [-V] <device>\n", prog);
exit(1);
}
const char* spec = NULL;
bool sb_only = false;
bool used_sectors = false;
+ const char* file_path = NULL;
- printf("dumpexfat %s\n", VERSION);
-
- while ((opt = getopt(argc, argv, "suV")) != -1)
+ while ((opt = getopt(argc, argv, "suf:V")) != -1)
{
switch (opt)
{
case 'u':
used_sectors = true;
break;
+ case 'f':
+ file_path = optarg;
+ break;
case 'V':
+ printf("dumpexfat %s\n", VERSION);
puts("Copyright (C) 2011-2016 Andrew Nayenko");
return 0;
default:
usage(argv[0]);
spec = argv[optind];
+ if (file_path)
+ return dump_file_fragments(spec, file_path);
+
if (sb_only)
return dump_sb(spec);
static int nodeck(struct exfat* ef, struct exfat_node* node)
{
const cluster_t cluster_size = CLUSTER_SIZE(*ef->sb);
- cluster_t clusters = (node->size + cluster_size - 1) / cluster_size;
+ cluster_t clusters = DIV_ROUND_UP(node->size, cluster_size);
cluster_t c = node->start_cluster;
int rc = 0;
static uint32_t bytes2clusters(const struct exfat* ef, uint64_t bytes)
{
uint64_t cluster_size = CLUSTER_SIZE(*ef->sb);
- return (bytes + cluster_size - 1) / cluster_size;
+ return DIV_ROUND_UP(bytes, cluster_size);
}
cluster_t exfat_next_cluster(const struct exfat* ef,
{
cluster_t cluster;
off_t offset;
- int contiguous;
char* chunk;
};
static int opendir(struct exfat* ef, const struct exfat_node* dir,
struct iterator* it)
{
+ char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
if (!(dir->flags & EXFAT_ATTRIB_DIR))
- exfat_bug("not a directory");
+ {
+ exfat_get_name(dir, buffer, sizeof(buffer) - 1);
+ exfat_bug("'%s' is not a directory", buffer);
+ }
+ if (CLUSTER_INVALID(dir->start_cluster))
+ {
+ exfat_get_name(dir, buffer, sizeof(buffer) - 1);
+ exfat_error("'%s' directory starts with invalid cluster %#x", buffer,
+ dir->start_cluster);
+ return -EIO;
+ }
it->cluster = dir->start_cluster;
it->offset = 0;
- it->contiguous = IS_CONTIGUOUS(*dir);
it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
if (it->chunk == NULL)
{
- exfat_error("out of memory");
+ exfat_error("failed to allocate memory for directory cluster");
return -ENOMEM;
}
if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
exfat_c2o(ef, it->cluster)) < 0)
{
- exfat_error("failed to read directory cluster %#x", it->cluster);
+ free(it->chunk);
+ exfat_get_name(dir, buffer, sizeof(buffer) - 1);
+ exfat_error("failed to read '%s' directory cluster %#x", buffer,
+ it->cluster);
return -EIO;
}
return 0;
{
it->cluster = 0;
it->offset = 0;
- it->contiguous = 0;
free(it->chunk);
it->chunk = NULL;
}
}
static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
- uint16_t reference_checksum, uint64_t valid_size)
+ uint16_t reference_checksum, uint64_t valid_size, int cluster_size)
{
char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+ bool ret = true;
/*
Validate checksum first. If it's invalid all other fields probably
exfat_get_name(node, buffer, sizeof(buffer) - 1);
exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
actual_checksum, reference_checksum);
- return false;
+ ret = false;
}
/*
exfat_get_name(node, buffer, sizeof(buffer) - 1);
exfat_error("'%s' has valid size (%"PRIu64") greater than size "
"(%"PRIu64")", buffer, valid_size, node->size);
- return false;
+ ret = false;
}
- return true;
+ /*
+ Empty file must have zero start cluster. Non-empty file must start
+ with a valid cluster. Directories cannot be empty (i.e. must always
+ have a valid start cluster), but we will check this later in opendir()
+ to give user a chance to read current directory.
+ */
+ if (node->size == 0 && node->start_cluster != EXFAT_CLUSTER_FREE)
+ {
+ exfat_get_name(node, buffer, sizeof(buffer) - 1);
+ exfat_error("'%s' is empty but start cluster is %#x", buffer,
+ node->start_cluster);
+ ret = false;
+ }
+ if (node->size > 0 && CLUSTER_INVALID(node->start_cluster))
+ {
+ exfat_get_name(node, buffer, sizeof(buffer) - 1);
+ exfat_error("'%s' points to invalid cluster %#x", buffer,
+ node->start_cluster);
+ ret = false;
+ }
+
+ /* Empty file or directory must be marked as non-contiguous. */
+ if (node->size == 0 && (node->flags & EXFAT_ATTRIB_CONTIGUOUS))
+ {
+ exfat_get_name(node, buffer, sizeof(buffer) - 1);
+ exfat_error("'%s' is empty but marked as contiguous (%#x)", buffer,
+ node->flags);
+ ret = false;
+ }
+
+ /* Directory size must be aligned on at cluster boundary. */
+ if ((node->flags & EXFAT_ATTRIB_DIR) && node->size % cluster_size != 0)
+ {
+ exfat_get_name(node, buffer, sizeof(buffer) - 1);
+ exfat_error("'%s' directory size %"PRIu64" is not divisible by %d", buffer,
+ node->size, cluster_size);
+ ret = false;
+ }
+
+ return ret;
}
static void decompress_upcase(uint16_t* output, const le16_t* source,
init_node_meta2(*node, meta2);
actual_checksum = exfat_add_checksum(entry, actual_checksum);
valid_size = le64_to_cpu(meta2->valid_size);
- /* empty files must be marked as non-contiguous */
- if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
- {
- exfat_error("empty file marked as contiguous (0x%hhx)",
- meta2->flags);
- goto error;
- }
- /* directories must be aligned on at cluster boundary */
- if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
- (*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
- {
- exfat_error("directory has invalid size %"PRIu64" bytes",
- (*node)->size);
- goto error;
- }
--continuations;
break;
if (--continuations == 0)
{
if (!check_node(*node, actual_checksum, reference_checksum,
- valid_size))
+ valid_size, CLUSTER_SIZE(*ef->sb)))
goto error;
if (!fetch_next_entry(ef, parent, it))
goto error;
void exfat_tzset(void)
{
time_t now;
+ struct tm* utc;
tzset();
now = time(NULL);
- exfat_timezone = mktime(gmtime(&now)) - now;
+ utc = gmtime(&now);
+ /* gmtime() always sets tm_isdst to 0 because daylight savings never
+ affect UTC. Setting tm_isdst to -1 makes mktime() to determine whether
+ summer time is in effect. */
+ utc->tm_isdst = -1;
+ exfat_timezone = mktime(utc) - now;
}