node.c (09.10.09)
exFAT file system implementation library.
- Copyright (C) 2010-2012 Andrew Nayenko
+ Free exFAT implementation.
+ Copyright (C) 2010-2013 Andrew Nayenko
- This program is free software: you can redistribute it and/or modify
+ This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
+ the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "exfat.h"
{
if (--node->references < 0)
{
- char buffer[EXFAT_NAME_MAX + 1];
- exfat_get_name(node, buffer, EXFAT_NAME_MAX);
+ char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+ exfat_get_name(node, buffer, sizeof(buffer) - 1);
exfat_bug("reference counter of `%s' is below zero", buffer);
}
if (node->flags & EXFAT_ATTRIB_UNLINKED)
{
/* free all clusters and node structure itself */
- exfat_truncate(ef, node, 0);
+ exfat_truncate(ef, node, 0, true);
free(node);
}
if (ef->cmap.dirty)
it->cluster = exfat_next_cluster(ef, parent, it->cluster);
if (CLUSTER_INVALID(it->cluster))
{
- exfat_error("invalid cluster while reading directory");
+ exfat_error("invalid cluster 0x%x while reading directory",
+ it->cluster);
return 1;
}
exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
le16_t* namep = NULL;
uint16_t reference_checksum = 0;
uint16_t actual_checksum = 0;
+ uint64_t real_size = 0;
*node = NULL;
}
init_node_meta2(*node, meta2);
actual_checksum = exfat_add_checksum(entry, actual_checksum);
- /* There are two fields that contain file size. Maybe they plan
- to add compression support in the future and one of those
- fields is visible (uncompressed) size and the other is real
- (compressed) size. Anyway, currently it looks like exFAT does
- not support compression and both fields must be equal. */
- if (le64_to_cpu(meta2->real_size) != (*node)->size)
- {
- exfat_error("real size does not equal to size "
- "(%"PRIu64" != %"PRIu64")",
- le64_to_cpu(meta2->real_size), (*node)->size);
- goto error;
- }
+ real_size = le64_to_cpu(meta2->real_size);
/* empty files must be marked as non-contiguous */
if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
{
if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
(*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
{
- char buffer[EXFAT_NAME_MAX + 1];
-
- exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
- exfat_error("directory `%s' has invalid size %"PRIu64" bytes",
- buffer, (*node)->size);
+ exfat_error("directory has invalid size %"PRIu64" bytes",
+ (*node)->size);
goto error;
}
--continuations;
namep += EXFAT_ENAME_MAX;
if (--continuations == 0)
{
+ /*
+ There are two fields that contain file size. Maybe they
+ plan to add compression support in the future and one of
+ those fields is visible (uncompressed) size and the other
+ is real (compressed) size. Anyway, currently it looks like
+ exFAT does not support compression and both fields must be
+ equal.
+
+ There is an exception though: pagefile.sys (its real_size
+ is always 0).
+ */
+ if (real_size != (*node)->size)
+ {
+ char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+ exfat_get_name(*node, buffer, sizeof(buffer) - 1);
+ exfat_error("`%s' real size does not equal to size "
+ "(%"PRIu64" != %"PRIu64")", buffer,
+ real_size, (*node)->size);
+ goto error;
+ }
if (actual_checksum != reference_checksum)
{
- exfat_error("invalid checksum (0x%hx != 0x%hx)",
- actual_checksum, reference_checksum);
+ char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+ exfat_get_name(*node, buffer, sizeof(buffer) - 1);
+ exfat_error("`%s' has invalid checksum (0x%hx != 0x%hx)",
+ buffer, actual_checksum, reference_checksum);
goto error;
}
if (fetch_next_entry(ef, parent, it) != 0)
upcase = (const struct exfat_entry_upcase*) entry;
if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
{
- exfat_error("invalid cluster in upcase table");
+ exfat_error("invalid cluster 0x%x in upcase table",
+ le32_to_cpu(upcase->start_cluster));
goto error;
}
if (le64_to_cpu(upcase->size) == 0 ||
case EXFAT_ENTRY_BITMAP:
bitmap = (const struct exfat_entry_bitmap*) entry;
- if (CLUSTER_INVALID(le32_to_cpu(bitmap->start_cluster)))
+ ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
+ if (CLUSTER_INVALID(ef->cmap.start_cluster))
{
- exfat_error("invalid cluster in clusters bitmap");
+ exfat_error("invalid cluster 0x%x in clusters bitmap",
+ ef->cmap.start_cluster);
goto error;
}
ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
le64_to_cpu(bitmap->size), (ef->cmap.size + 7) / 8);
goto error;
}
- ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
/* FIXME bitmap can be rather big, up to 512 MB */
ef->cmap.chunk_size = ef->cmap.size;
ef->cmap.chunk = malloc(le64_to_cpu(bitmap->size));
goto error;
}
if (utf16_to_utf8(ef->label, label->name,
- sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
+ sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0)
goto error;
break;
return 0;
}
-static void reset_cache(struct exfat* ef, struct exfat_node* node)
+static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
+{
+ node->parent = dir;
+ if (dir->child)
+ {
+ dir->child->prev = node;
+ node->next = dir->child;
+ }
+ dir->child = node;
+}
+
+static void tree_detach(struct exfat_node* node)
{
- struct exfat_node* child;
- struct exfat_node* next;
+ if (node->prev)
+ node->prev->next = node->next;
+ else /* this is the first node in the list */
+ node->parent->child = node->next;
+ if (node->next)
+ node->next->prev = node->prev;
+ node->parent = NULL;
+ node->prev = NULL;
+ node->next = NULL;
+}
- for (child = node->child; child; child = next)
+static void reset_cache(struct exfat* ef, struct exfat_node* node)
+{
+ while (node->child)
{
- reset_cache(ef, child);
- next = child->next;
- free(child);
+ struct exfat_node* p = node->child;
+ reset_cache(ef, p);
+ tree_detach(p);
+ free(p);
}
+ node->flags &= ~EXFAT_ATTRIB_CACHED;
if (node->references != 0)
{
- char buffer[EXFAT_NAME_MAX + 1];
- exfat_get_name(node, buffer, EXFAT_NAME_MAX);
+ char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+ exfat_get_name(node, buffer, sizeof(buffer) - 1);
exfat_warn("non-zero reference counter (%d) for `%s'",
node->references, buffer);
}
- while (node->references--)
+ while (node->references)
exfat_put_node(ef, node);
- node->child = NULL;
- node->flags &= ~EXFAT_ATTRIB_CACHED;
}
void exfat_reset_cache(struct exfat* ef)
reset_cache(ef, ef->root);
}
-void next_entry(struct exfat* ef, const struct exfat_node* parent,
+static void next_entry(struct exfat* ef, const struct exfat_node* parent,
cluster_t* cluster, off_t* offset)
{
*offset += sizeof(struct exfat_entry);
}
}
-static void tree_detach(struct exfat_node* node)
-{
- if (node->prev)
- node->prev->next = node->next;
- else /* this is the first node in the list */
- node->parent->child = node->next;
- if (node->next)
- node->next->prev = node->prev;
- node->parent = NULL;
- node->prev = NULL;
- node->next = NULL;
-}
-
-static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
-{
- node->parent = dir;
- if (dir->child)
- {
- dir->child->prev = node;
- node->next = dir->child;
- }
- dir->child = node;
-}
-
static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
off_t deleted_offset)
{
new_size = CLUSTER_SIZE(*ef->sb);
if (new_size == dir->size)
return 0;
- rc = exfat_truncate(ef, dir, new_size);
+ rc = exfat_truncate(ef, dir, new_size, true);
if (rc != 0)
return rc;
return 0;
{
return exfat_truncate(ef, dir,
DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
- * CLUSTER_SIZE(*ef->sb));
+ * CLUSTER_SIZE(*ef->sb), true);
}
static int find_slot(struct exfat* ef, struct exfat_node* dir,
if (rc != 0)
return 0;
/* directories always have at least one cluster */
- rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb));
+ rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb), true);
if (rc != 0)
{
delete(ef, node);
exfat_put_node(ef, node);
return rc;
}
+
+ /* check that target is not a subdirectory of the source */
+ if (node->flags & EXFAT_ATTRIB_DIR)
+ {
+ struct exfat_node* p;
+
+ for (p = dir; p; p = p->parent)
+ if (node == p)
+ {
+ if (existing != NULL)
+ exfat_put_node(ef, existing);
+ exfat_put_node(ef, dir);
+ exfat_put_node(ef, node);
+ return -EINVAL;
+ }
+ }
+
if (existing != NULL)
{
- if (existing->flags & EXFAT_ATTRIB_DIR)
+ /* remove target if it's not the same node as source */
+ if (existing != node)
{
- if (node->flags & EXFAT_ATTRIB_DIR)
- rc = exfat_rmdir(ef, existing);
+ if (existing->flags & EXFAT_ATTRIB_DIR)
+ {
+ if (node->flags & EXFAT_ATTRIB_DIR)
+ rc = exfat_rmdir(ef, existing);
+ else
+ rc = -ENOTDIR;
+ }
else
- rc = -ENOTDIR;
+ {
+ if (!(node->flags & EXFAT_ATTRIB_DIR))
+ rc = exfat_unlink(ef, existing);
+ else
+ rc = -EISDIR;
+ }
+ exfat_put_node(ef, existing);
+ if (rc != 0)
+ {
+ exfat_put_node(ef, dir);
+ exfat_put_node(ef, node);
+ return rc;
+ }
}
else
- {
- if (!(node->flags & EXFAT_ATTRIB_DIR))
- rc = exfat_unlink(ef, existing);
- else
- rc = -EISDIR;
- }
- exfat_put_node(ef, existing);
- if (rc != 0)
- {
- exfat_put_node(ef, dir);
- exfat_put_node(ef, node);
- return rc;
- }
+ exfat_put_node(ef, existing);
}
rc = find_slot(ef, dir, &cluster, &offset,
exfat_pwrite(ef->dev, &entry, sizeof(struct exfat_entry_label),
co2o(ef, cluster, offset));
+ strcpy(ef->label, label);
return 0;
}