+static int rename_entry(struct exfat* ef, struct exfat_node* dir,
+ struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
+ off_t new_offset)
+{
+ struct exfat_entry_meta1 meta1;
+ struct exfat_entry_meta2 meta2;
+ cluster_t old_cluster = node->entry_cluster;
+ off_t old_offset = node->entry_offset;
+ const size_t name_length = utf16_length(name);
+ const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+ int i;
+
+ if (exfat_pread(ef->dev, &meta1, sizeof(meta1),
+ co2o(ef, old_cluster, old_offset)) < 0)
+ {
+ exfat_error("failed to read meta1 entry on rename");
+ return -EIO;
+ }
+ if (!next_entry(ef, node->parent, &old_cluster, &old_offset))
+ return -EIO;
+ if (exfat_pread(ef->dev, &meta2, sizeof(meta2),
+ co2o(ef, old_cluster, old_offset)) < 0)
+ {
+ exfat_error("failed to read meta2 entry on rename");
+ return -EIO;
+ }
+ meta1.continuations = 1 + name_entries;
+ meta2.name_hash = exfat_calc_name_hash(ef, name);
+ meta2.name_length = name_length;
+ meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
+
+ if (!erase_entry(ef, node))
+ return -EIO;
+
+ node->entry_cluster = new_cluster;
+ node->entry_offset = new_offset;
+
+ if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1),
+ co2o(ef, new_cluster, new_offset)) < 0)
+ {
+ exfat_error("failed to write meta1 entry on rename");
+ return -EIO;
+ }
+ if (!next_entry(ef, dir, &new_cluster, &new_offset))
+ return -EIO;
+ if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2),
+ co2o(ef, new_cluster, new_offset)) < 0)
+ {
+ exfat_error("failed to write meta2 entry on rename");
+ return -EIO;
+ }
+
+ for (i = 0; i < name_entries; i++)
+ {
+ struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
+ memcpy(name_entry.name, name + i * EXFAT_ENAME_MAX,
+ EXFAT_ENAME_MAX * sizeof(le16_t));
+ if (!next_entry(ef, dir, &new_cluster, &new_offset))
+ return -EIO;
+ if (exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
+ co2o(ef, new_cluster, new_offset)) < 0)
+ {
+ exfat_error("failed to write name entry on rename");
+ return -EIO;
+ }
+ }
+
+ memcpy(node->name, name, (EXFAT_NAME_MAX + 1) * sizeof(le16_t));
+ tree_detach(node);
+ tree_attach(dir, node);
+ return 0;
+}
+
+int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
+{
+ struct exfat_node* node;
+ struct exfat_node* existing;
+ struct exfat_node* dir;
+ cluster_t cluster = EXFAT_CLUSTER_BAD;
+ off_t offset = -1;
+ le16_t name[EXFAT_NAME_MAX + 1];
+ int rc;
+
+ rc = exfat_lookup(ef, &node, old_path);
+ if (rc != 0)
+ return rc;
+
+ rc = exfat_split(ef, &dir, &existing, name, new_path);
+ if (rc != 0)
+ {
+ 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)
+ {
+ /* remove target if it's not the same node as source */
+ if (existing != node)
+ {
+ if (existing->flags & EXFAT_ATTRIB_DIR)
+ {
+ if (node->flags & EXFAT_ATTRIB_DIR)
+ rc = exfat_rmdir(ef, existing);
+ else
+ rc = -ENOTDIR;
+ }
+ 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;
+ }
+ }
+ else
+ exfat_put_node(ef, existing);
+ }
+
+ rc = find_slot(ef, dir, &cluster, &offset,
+ 2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
+ if (rc != 0)
+ {
+ exfat_put_node(ef, dir);
+ exfat_put_node(ef, node);
+ return rc;
+ }
+ rc = rename_entry(ef, dir, node, name, cluster, offset);
+ exfat_put_node(ef, dir);
+ exfat_put_node(ef, node);
+ return rc;
+}
+