aboutsummaryrefslogtreecommitdiffstats
path: root/roms/u-boot/fs/fat/fat_write.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/u-boot/fs/fat/fat_write.c')
-rw-r--r--roms/u-boot/fs/fat/fat_write.c1773
1 files changed, 1773 insertions, 0 deletions
diff --git a/roms/u-boot/fs/fat/fat_write.c b/roms/u-boot/fs/fat/fat_write.c
new file mode 100644
index 000000000..8ff2f6def
--- /dev/null
+++ b/roms/u-boot/fs/fat/fat_write.c
@@ -0,0 +1,1773 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * fat_write.c
+ *
+ * R/W (V)FAT 12/16/32 filesystem implementation by Donggeun Kim
+ */
+
+#include <common.h>
+#include <command.h>
+#include <config.h>
+#include <div64.h>
+#include <fat.h>
+#include <log.h>
+#include <malloc.h>
+#include <part.h>
+#include <rand.h>
+#include <asm/byteorder.h>
+#include <asm/cache.h>
+#include <linux/ctype.h>
+#include <linux/math64.h>
+#include "fat.c"
+
+static dir_entry *find_directory_entry(fat_itr *itr, char *filename);
+static int new_dir_table(fat_itr *itr);
+
+/* Characters that may only be used in long file names */
+static const char LONG_ONLY_CHARS[] = "+,;=[]";
+
+/* Combined size of the name and ext fields in the directory entry */
+#define SHORT_NAME_SIZE 11
+
+/**
+ * str2fat() - convert string to valid FAT name characters
+ *
+ * Stop when reaching end of @src or a period.
+ * Ignore spaces.
+ * Replace characters that may only be used in long names by underscores.
+ * Convert lower case characters to upper case.
+ *
+ * To avoid assumptions about the code page we do not use characters
+ * above 0x7f for the short name.
+ *
+ * @dest: destination buffer
+ * @src: source buffer
+ * @length: size of destination buffer
+ * Return: number of bytes in destination buffer
+ */
+static int str2fat(char *dest, char *src, int length)
+{
+ int i;
+
+ for (i = 0; i < length; ++src) {
+ char c = *src;
+
+ if (!c || c == '.')
+ break;
+ if (c == ' ')
+ continue;
+ if (strchr(LONG_ONLY_CHARS, c) || c > 0x7f)
+ c = '_';
+ else if (c >= 'a' && c <= 'z')
+ c &= 0xdf;
+ dest[i] = c;
+ ++i;
+ }
+ return i;
+}
+
+/**
+ * fat_move_to_cluster() - position to first directory entry in cluster
+ *
+ * @itr: directory iterator
+ * @cluster cluster
+ * Return: 0 for success, -EIO on error
+ */
+static int fat_move_to_cluster(fat_itr *itr, unsigned int cluster)
+{
+ unsigned int nbytes;
+
+ /* position to the start of the directory */
+ itr->next_clust = cluster;
+ itr->last_cluster = 0;
+ if (!fat_next_cluster(itr, &nbytes))
+ return -EIO;
+ itr->dent = (dir_entry *)itr->block;
+ itr->remaining = nbytes / sizeof(dir_entry) - 1;
+ return 0;
+}
+
+/**
+ * set_name() - set short name in directory entry
+ *
+ * The function determines if the @filename is a valid short name.
+ * In this case no long name is needed.
+ *
+ * If a long name is needed, a short name is constructed.
+ *
+ * @itr: directory iterator
+ * @filename: long file name
+ * @shortname: buffer of 11 bytes to receive chosen short name and extension
+ * Return: number of directory entries needed, negative on error
+ */
+static int set_name(fat_itr *itr, const char *filename, char *shortname)
+{
+ char *period;
+ char *pos;
+ int period_location;
+ char buf[13];
+ int i;
+ int ret;
+ struct nameext dirent;
+
+ if (!filename)
+ return -EIO;
+
+ /* Initialize buffer */
+ memset(&dirent, ' ', sizeof(dirent));
+
+ /* Convert filename to upper case short name */
+ period = strrchr(filename, '.');
+ pos = (char *)filename;
+ if (*pos == '.') {
+ pos = period + 1;
+ period = 0;
+ }
+ if (period)
+ str2fat(dirent.ext, period + 1, sizeof(dirent.ext));
+ period_location = str2fat(dirent.name, pos, sizeof(dirent.name));
+ if (period_location < 0)
+ return period_location;
+ if (*dirent.name == ' ')
+ *dirent.name = '_';
+ /* 0xe5 signals a deleted directory entry. Replace it by 0x05. */
+ if (*dirent.name == 0xe5)
+ *dirent.name = 0x05;
+
+ /* If filename and short name are the same, quit. */
+ sprintf(buf, "%.*s.%.3s", period_location, dirent.name, dirent.ext);
+ if (!strcmp(buf, filename)) {
+ ret = 1;
+ goto out;
+ }
+
+ /* Construct an indexed short name */
+ for (i = 1; i < 0x200000; ++i) {
+ int suffix_len;
+ int suffix_start;
+ int j;
+
+ /* To speed up the search use random numbers */
+ if (i < 10) {
+ j = i;
+ } else {
+ j = 30 - fls(i);
+ j = 10 + (rand() >> j);
+ }
+ sprintf(buf, "~%d", j);
+ suffix_len = strlen(buf);
+ suffix_start = 8 - suffix_len;
+ if (suffix_start > period_location)
+ suffix_start = period_location;
+ memcpy(dirent.name + suffix_start, buf, suffix_len);
+ if (*dirent.ext != ' ')
+ sprintf(buf, "%.*s.%.3s", suffix_start + suffix_len,
+ dirent.name, dirent.ext);
+ else
+ sprintf(buf, "%.*s", suffix_start + suffix_len,
+ dirent.name);
+ debug("generated short name: %s\n", buf);
+
+ /* Check that the short name does not exist yet. */
+ ret = fat_move_to_cluster(itr, itr->start_clust);
+ if (ret)
+ return ret;
+ if (find_directory_entry(itr, buf))
+ continue;
+
+ debug("chosen short name: %s\n", buf);
+ /* Each long name directory entry takes 13 characters. */
+ ret = (strlen(filename) + 25) / 13;
+ goto out;
+ }
+ return -EIO;
+out:
+ memcpy(shortname, &dirent, SHORT_NAME_SIZE);
+ return ret;
+}
+
+static int total_sector;
+static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
+{
+ ulong ret;
+
+ if (!cur_dev)
+ return -1;
+
+ if (cur_part_info.start + block + nr_blocks >
+ cur_part_info.start + total_sector) {
+ printf("error: overflow occurs\n");
+ return -1;
+ }
+
+ ret = blk_dwrite(cur_dev, cur_part_info.start + block, nr_blocks, buf);
+ if (nr_blocks && ret == 0)
+ return -1;
+
+ return ret;
+}
+
+/*
+ * Write fat buffer into block device
+ */
+static int flush_dirty_fat_buffer(fsdata *mydata)
+{
+ int getsize = FATBUFBLOCKS;
+ __u32 fatlength = mydata->fatlength;
+ __u8 *bufptr = mydata->fatbuf;
+ __u32 startblock = mydata->fatbufnum * FATBUFBLOCKS;
+
+ debug("debug: evicting %d, dirty: %d\n", mydata->fatbufnum,
+ (int)mydata->fat_dirty);
+
+ if ((!mydata->fat_dirty) || (mydata->fatbufnum == -1))
+ return 0;
+
+ /* Cap length if fatlength is not a multiple of FATBUFBLOCKS */
+ if (startblock + getsize > fatlength)
+ getsize = fatlength - startblock;
+
+ startblock += mydata->fat_sect;
+
+ /* Write FAT buf */
+ if (disk_write(startblock, getsize, bufptr) < 0) {
+ debug("error: writing FAT blocks\n");
+ return -1;
+ }
+
+ if (mydata->fats == 2) {
+ /* Update corresponding second FAT blocks */
+ startblock += mydata->fatlength;
+ if (disk_write(startblock, getsize, bufptr) < 0) {
+ debug("error: writing second FAT blocks\n");
+ return -1;
+ }
+ }
+ mydata->fat_dirty = 0;
+
+ return 0;
+}
+
+/**
+ * fat_find_empty_dentries() - find a sequence of available directory entries
+ *
+ * @itr: directory iterator
+ * @count: number of directory entries to find
+ * Return: 0 on success or negative error number
+ */
+static int fat_find_empty_dentries(fat_itr *itr, int count)
+{
+ unsigned int cluster;
+ dir_entry *dent;
+ int remaining;
+ unsigned int n = 0;
+ int ret;
+
+ ret = fat_move_to_cluster(itr, itr->start_clust);
+ if (ret)
+ return ret;
+
+ for (;;) {
+ if (!itr->dent) {
+ log_debug("Not enough directory entries available\n");
+ return -ENOSPC;
+ }
+ switch (itr->dent->nameext.name[0]) {
+ case 0x00:
+ case DELETED_FLAG:
+ if (!n) {
+ /* Remember first deleted directory entry */
+ cluster = itr->clust;
+ dent = itr->dent;
+ remaining = itr->remaining;
+ }
+ ++n;
+ if (n == count)
+ goto out;
+ break;
+ default:
+ n = 0;
+ break;
+ }
+
+ next_dent(itr);
+ if (!itr->dent &&
+ (!itr->is_root || itr->fsdata->fatsize == 32) &&
+ new_dir_table(itr))
+ return -ENOSPC;
+ }
+out:
+ /* Position back to first directory entry */
+ if (itr->clust != cluster) {
+ ret = fat_move_to_cluster(itr, cluster);
+ if (ret)
+ return ret;
+ }
+ itr->dent = dent;
+ itr->remaining = remaining;
+ return 0;
+}
+
+/*
+ * Set the file name information from 'name' into 'slotptr',
+ */
+static int str2slot(dir_slot *slotptr, const char *name, int *idx)
+{
+ int j, end_idx = 0;
+
+ for (j = 0; j <= 8; j += 2) {
+ if (name[*idx] == 0x00) {
+ slotptr->name0_4[j] = 0;
+ slotptr->name0_4[j + 1] = 0;
+ end_idx++;
+ goto name0_4;
+ }
+ slotptr->name0_4[j] = name[*idx];
+ (*idx)++;
+ end_idx++;
+ }
+ for (j = 0; j <= 10; j += 2) {
+ if (name[*idx] == 0x00) {
+ slotptr->name5_10[j] = 0;
+ slotptr->name5_10[j + 1] = 0;
+ end_idx++;
+ goto name5_10;
+ }
+ slotptr->name5_10[j] = name[*idx];
+ (*idx)++;
+ end_idx++;
+ }
+ for (j = 0; j <= 2; j += 2) {
+ if (name[*idx] == 0x00) {
+ slotptr->name11_12[j] = 0;
+ slotptr->name11_12[j + 1] = 0;
+ end_idx++;
+ goto name11_12;
+ }
+ slotptr->name11_12[j] = name[*idx];
+ (*idx)++;
+ end_idx++;
+ }
+
+ if (name[*idx] == 0x00)
+ return 1;
+
+ return 0;
+/* Not used characters are filled with 0xff 0xff */
+name0_4:
+ for (; end_idx < 5; end_idx++) {
+ slotptr->name0_4[end_idx * 2] = 0xff;
+ slotptr->name0_4[end_idx * 2 + 1] = 0xff;
+ }
+ end_idx = 5;
+name5_10:
+ end_idx -= 5;
+ for (; end_idx < 6; end_idx++) {
+ slotptr->name5_10[end_idx * 2] = 0xff;
+ slotptr->name5_10[end_idx * 2 + 1] = 0xff;
+ }
+ end_idx = 11;
+name11_12:
+ end_idx -= 11;
+ for (; end_idx < 2; end_idx++) {
+ slotptr->name11_12[end_idx * 2] = 0xff;
+ slotptr->name11_12[end_idx * 2 + 1] = 0xff;
+ }
+
+ return 1;
+}
+
+static int flush_dir(fat_itr *itr);
+
+/**
+ * fill_dir_slot() - fill directory entries for long name
+ *
+ * @itr: directory iterator
+ * @l_name: long name
+ * @shortname: short name
+ * Return: 0 for success, -errno otherwise
+ */
+static int
+fill_dir_slot(fat_itr *itr, const char *l_name, const char *shortname)
+{
+ __u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)];
+ dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer;
+ __u8 counter = 0, checksum;
+ int idx = 0, ret;
+
+ /* Get short file name checksum value */
+ checksum = mkcksum((void *)shortname);
+
+ do {
+ memset(slotptr, 0x00, sizeof(dir_slot));
+ ret = str2slot(slotptr, l_name, &idx);
+ slotptr->id = ++counter;
+ slotptr->attr = ATTR_VFAT;
+ slotptr->alias_checksum = checksum;
+ slotptr++;
+ } while (ret == 0);
+
+ slotptr--;
+ slotptr->id |= LAST_LONG_ENTRY_MASK;
+
+ while (counter >= 1) {
+ memcpy(itr->dent, slotptr, sizeof(dir_slot));
+ slotptr--;
+ counter--;
+
+ if (!itr->remaining) {
+ /* Write directory table to device */
+ ret = flush_dir(itr);
+ if (ret)
+ return ret;
+ }
+
+ next_dent(itr);
+ if (!itr->dent)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Set the entry at index 'entry' in a FAT (12/16/32) table.
+ */
+static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value)
+{
+ __u32 bufnum, offset, off16;
+ __u16 val1, val2;
+
+ switch (mydata->fatsize) {
+ case 32:
+ bufnum = entry / FAT32BUFSIZE;
+ offset = entry - bufnum * FAT32BUFSIZE;
+ break;
+ case 16:
+ bufnum = entry / FAT16BUFSIZE;
+ offset = entry - bufnum * FAT16BUFSIZE;
+ break;
+ case 12:
+ bufnum = entry / FAT12BUFSIZE;
+ offset = entry - bufnum * FAT12BUFSIZE;
+ break;
+ default:
+ /* Unsupported FAT size */
+ return -1;
+ }
+
+ /* Read a new block of FAT entries into the cache. */
+ if (bufnum != mydata->fatbufnum) {
+ int getsize = FATBUFBLOCKS;
+ __u8 *bufptr = mydata->fatbuf;
+ __u32 fatlength = mydata->fatlength;
+ __u32 startblock = bufnum * FATBUFBLOCKS;
+
+ /* Cap length if fatlength is not a multiple of FATBUFBLOCKS */
+ if (startblock + getsize > fatlength)
+ getsize = fatlength - startblock;
+
+ if (flush_dirty_fat_buffer(mydata) < 0)
+ return -1;
+
+ startblock += mydata->fat_sect;
+
+ if (disk_read(startblock, getsize, bufptr) < 0) {
+ debug("Error reading FAT blocks\n");
+ return -1;
+ }
+ mydata->fatbufnum = bufnum;
+ }
+
+ /* Mark as dirty */
+ mydata->fat_dirty = 1;
+
+ /* Set the actual entry */
+ switch (mydata->fatsize) {
+ case 32:
+ ((__u32 *) mydata->fatbuf)[offset] = cpu_to_le32(entry_value);
+ break;
+ case 16:
+ ((__u16 *) mydata->fatbuf)[offset] = cpu_to_le16(entry_value);
+ break;
+ case 12:
+ off16 = (offset * 3) / 4;
+
+ switch (offset & 0x3) {
+ case 0:
+ val1 = cpu_to_le16(entry_value) & 0xfff;
+ ((__u16 *)mydata->fatbuf)[off16] &= ~0xfff;
+ ((__u16 *)mydata->fatbuf)[off16] |= val1;
+ break;
+ case 1:
+ val1 = cpu_to_le16(entry_value) & 0xf;
+ val2 = (cpu_to_le16(entry_value) >> 4) & 0xff;
+
+ ((__u16 *)mydata->fatbuf)[off16] &= ~0xf000;
+ ((__u16 *)mydata->fatbuf)[off16] |= (val1 << 12);
+
+ ((__u16 *)mydata->fatbuf)[off16 + 1] &= ~0xff;
+ ((__u16 *)mydata->fatbuf)[off16 + 1] |= val2;
+ break;
+ case 2:
+ val1 = cpu_to_le16(entry_value) & 0xff;
+ val2 = (cpu_to_le16(entry_value) >> 8) & 0xf;
+
+ ((__u16 *)mydata->fatbuf)[off16] &= ~0xff00;
+ ((__u16 *)mydata->fatbuf)[off16] |= (val1 << 8);
+
+ ((__u16 *)mydata->fatbuf)[off16 + 1] &= ~0xf;
+ ((__u16 *)mydata->fatbuf)[off16 + 1] |= val2;
+ break;
+ case 3:
+ val1 = cpu_to_le16(entry_value) & 0xfff;
+ ((__u16 *)mydata->fatbuf)[off16] &= ~0xfff0;
+ ((__u16 *)mydata->fatbuf)[off16] |= (val1 << 4);
+ break;
+ default:
+ break;
+ }
+
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Determine the next free cluster after 'entry' in a FAT (12/16/32) table
+ * and link it to 'entry'. EOC marker is not set on returned entry.
+ */
+static __u32 determine_fatent(fsdata *mydata, __u32 entry)
+{
+ __u32 next_fat, next_entry = entry + 1;
+
+ while (1) {
+ next_fat = get_fatent(mydata, next_entry);
+ if (next_fat == 0) {
+ /* found free entry, link to entry */
+ set_fatent_value(mydata, entry, next_entry);
+ break;
+ }
+ next_entry++;
+ }
+ debug("FAT%d: entry: %08x, entry_value: %04x\n",
+ mydata->fatsize, entry, next_entry);
+
+ return next_entry;
+}
+
+/**
+ * set_sectors() - write data to sectors
+ *
+ * Write 'size' bytes from 'buffer' into the specified sector.
+ *
+ * @mydata: data to be written
+ * @startsect: sector to be written to
+ * @buffer: data to be written
+ * @size: bytes to be written (but not more than the size of a cluster)
+ * Return: 0 on success, -1 otherwise
+ */
+static int
+set_sectors(fsdata *mydata, u32 startsect, u8 *buffer, u32 size)
+{
+ int ret;
+
+ debug("startsect: %d\n", startsect);
+
+ if ((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1)) {
+ ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
+
+ debug("FAT: Misaligned buffer address (%p)\n", buffer);
+
+ while (size >= mydata->sect_size) {
+ memcpy(tmpbuf, buffer, mydata->sect_size);
+ ret = disk_write(startsect++, 1, tmpbuf);
+ if (ret != 1) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ buffer += mydata->sect_size;
+ size -= mydata->sect_size;
+ }
+ } else if (size >= mydata->sect_size) {
+ u32 nsects;
+
+ nsects = size / mydata->sect_size;
+ ret = disk_write(startsect, nsects, buffer);
+ if (ret != nsects) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ startsect += nsects;
+ buffer += nsects * mydata->sect_size;
+ size -= nsects * mydata->sect_size;
+ }
+
+ if (size) {
+ ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
+ /* Do not leak content of stack */
+ memset(tmpbuf, 0, mydata->sect_size);
+ memcpy(tmpbuf, buffer, size);
+ ret = disk_write(startsect, 1, tmpbuf);
+ if (ret != 1) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * set_cluster() - write data to cluster
+ *
+ * Write 'size' bytes from 'buffer' into the specified cluster.
+ *
+ * @mydata: data to be written
+ * @clustnum: cluster to be written to
+ * @buffer: data to be written
+ * @size: bytes to be written (but not more than the size of a cluster)
+ * Return: 0 on success, -1 otherwise
+ */
+static int
+set_cluster(fsdata *mydata, u32 clustnum, u8 *buffer, u32 size)
+{
+ return set_sectors(mydata, clust_to_sect(mydata, clustnum),
+ buffer, size);
+}
+
+/**
+ * flush_dir() - flush directory
+ *
+ * @itr: directory iterator
+ * Return: 0 for success, -EIO on error
+ */
+static int flush_dir(fat_itr *itr)
+{
+ fsdata *mydata = itr->fsdata;
+ u32 startsect, sect_offset, nsects;
+ int ret;
+
+ if (!itr->is_root || mydata->fatsize == 32) {
+ ret = set_cluster(mydata, itr->clust, itr->block,
+ mydata->clust_size * mydata->sect_size);
+ goto out;
+ }
+
+ sect_offset = itr->clust * mydata->clust_size;
+ startsect = mydata->rootdir_sect + sect_offset;
+ /* do not write past the end of rootdir */
+ nsects = min_t(u32, mydata->clust_size,
+ mydata->rootdir_size - sect_offset);
+
+ ret = set_sectors(mydata, startsect, itr->block,
+ nsects * mydata->sect_size);
+out:
+ if (ret) {
+ log_err("Error: writing directory entry\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ * Read and modify data on existing and consecutive cluster blocks
+ */
+static int
+get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer,
+ loff_t size, loff_t *gotsize)
+{
+ static u8 *tmpbuf_cluster;
+ unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
+ __u32 startsect;
+ loff_t wsize;
+ int clustcount, i, ret;
+
+ *gotsize = 0;
+ if (!size)
+ return 0;
+
+ if (!tmpbuf_cluster) {
+ tmpbuf_cluster = memalign(ARCH_DMA_MINALIGN, MAX_CLUSTSIZE);
+ if (!tmpbuf_cluster)
+ return -1;
+ }
+
+ assert(pos < bytesperclust);
+ startsect = clust_to_sect(mydata, clustnum);
+
+ debug("clustnum: %d, startsect: %d, pos: %lld\n",
+ clustnum, startsect, pos);
+
+ /* partial write at beginning */
+ if (pos) {
+ wsize = min(bytesperclust - pos, size);
+ ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error reading data (got %d)\n", ret);
+ return -1;
+ }
+
+ memcpy(tmpbuf_cluster + pos, buffer, wsize);
+ ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ size -= wsize;
+ buffer += wsize;
+ *gotsize += wsize;
+
+ startsect += mydata->clust_size;
+
+ if (!size)
+ return 0;
+ }
+
+ /* full-cluster write */
+ if (size >= bytesperclust) {
+ clustcount = lldiv(size, bytesperclust);
+
+ if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) {
+ wsize = clustcount * bytesperclust;
+ ret = disk_write(startsect,
+ clustcount * mydata->clust_size,
+ buffer);
+ if (ret != clustcount * mydata->clust_size) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ size -= wsize;
+ buffer += wsize;
+ *gotsize += wsize;
+
+ startsect += clustcount * mydata->clust_size;
+ } else {
+ for (i = 0; i < clustcount; i++) {
+ memcpy(tmpbuf_cluster, buffer, bytesperclust);
+ ret = disk_write(startsect,
+ mydata->clust_size,
+ tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error writing data (got %d)\n",
+ ret);
+ return -1;
+ }
+
+ size -= bytesperclust;
+ buffer += bytesperclust;
+ *gotsize += bytesperclust;
+
+ startsect += mydata->clust_size;
+ }
+ }
+ }
+
+ /* partial write at end */
+ if (size) {
+ wsize = size;
+ ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error reading data (got %d)\n", ret);
+ return -1;
+ }
+ memcpy(tmpbuf_cluster, buffer, wsize);
+ ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ size -= wsize;
+ *gotsize += wsize;
+ }
+
+ assert(!size);
+
+ return 0;
+}
+
+/*
+ * Find the first empty cluster
+ */
+static int find_empty_cluster(fsdata *mydata)
+{
+ __u32 fat_val, entry = 3;
+
+ while (1) {
+ fat_val = get_fatent(mydata, entry);
+ if (fat_val == 0)
+ break;
+ entry++;
+ }
+
+ return entry;
+}
+
+/**
+ * new_dir_table() - allocate a cluster for additional directory entries
+ *
+ * @itr: directory iterator
+ * Return: 0 on success, -EIO otherwise
+ */
+static int new_dir_table(fat_itr *itr)
+{
+ fsdata *mydata = itr->fsdata;
+ int dir_newclust = 0;
+ int dir_oldclust = itr->clust;
+ unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
+
+ dir_newclust = find_empty_cluster(mydata);
+
+ /*
+ * Flush before updating FAT to ensure valid directory structure
+ * in case of failure.
+ */
+ itr->clust = dir_newclust;
+ itr->next_clust = dir_newclust;
+ memset(itr->block, 0x00, bytesperclust);
+ if (flush_dir(itr))
+ return -EIO;
+
+ set_fatent_value(mydata, dir_oldclust, dir_newclust);
+ if (mydata->fatsize == 32)
+ set_fatent_value(mydata, dir_newclust, 0xffffff8);
+ else if (mydata->fatsize == 16)
+ set_fatent_value(mydata, dir_newclust, 0xfff8);
+ else if (mydata->fatsize == 12)
+ set_fatent_value(mydata, dir_newclust, 0xff8);
+
+ if (flush_dirty_fat_buffer(mydata) < 0)
+ return -EIO;
+
+ itr->dent = (dir_entry *)itr->block;
+ itr->last_cluster = 1;
+ itr->remaining = bytesperclust / sizeof(dir_entry) - 1;
+
+ return 0;
+}
+
+/*
+ * Set empty cluster from 'entry' to the end of a file
+ */
+static int clear_fatent(fsdata *mydata, __u32 entry)
+{
+ __u32 fat_val;
+
+ while (!CHECK_CLUST(entry, mydata->fatsize)) {
+ fat_val = get_fatent(mydata, entry);
+ if (fat_val != 0)
+ set_fatent_value(mydata, entry, 0);
+ else
+ break;
+
+ entry = fat_val;
+ }
+
+ /* Flush fat buffer */
+ if (flush_dirty_fat_buffer(mydata) < 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Set start cluster in directory entry
+ */
+static void set_start_cluster(const fsdata *mydata, dir_entry *dentptr,
+ __u32 start_cluster)
+{
+ if (mydata->fatsize == 32)
+ dentptr->starthi =
+ cpu_to_le16((start_cluster & 0xffff0000) >> 16);
+ dentptr->start = cpu_to_le16(start_cluster & 0xffff);
+}
+
+/*
+ * Check whether adding a file makes the file system to
+ * exceed the size of the block device
+ * Return -1 when overflow occurs, otherwise return 0
+ */
+static int check_overflow(fsdata *mydata, __u32 clustnum, loff_t size)
+{
+ __u32 startsect, sect_num, offset;
+
+ if (clustnum > 0)
+ startsect = clust_to_sect(mydata, clustnum);
+ else
+ startsect = mydata->rootdir_sect;
+
+ sect_num = div_u64_rem(size, mydata->sect_size, &offset);
+
+ if (offset != 0)
+ sect_num++;
+
+ if (startsect + sect_num > total_sector)
+ return -1;
+ return 0;
+}
+
+/*
+ * Write at most 'maxsize' bytes from 'buffer' into
+ * the file associated with 'dentptr'
+ * Update the number of bytes written in *gotsize and return 0
+ * or return -1 on fatal errors.
+ */
+static int
+set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer,
+ loff_t maxsize, loff_t *gotsize)
+{
+ unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
+ __u32 curclust = START(dentptr);
+ __u32 endclust = 0, newclust = 0;
+ u64 cur_pos, filesize;
+ loff_t offset, actsize, wsize;
+
+ *gotsize = 0;
+ filesize = pos + maxsize;
+
+ debug("%llu bytes\n", filesize);
+
+ if (!filesize) {
+ if (!curclust)
+ return 0;
+ if (!CHECK_CLUST(curclust, mydata->fatsize) ||
+ IS_LAST_CLUST(curclust, mydata->fatsize)) {
+ clear_fatent(mydata, curclust);
+ set_start_cluster(mydata, dentptr, 0);
+ return 0;
+ }
+ debug("curclust: 0x%x\n", curclust);
+ debug("Invalid FAT entry\n");
+ return -1;
+ }
+
+ if (!curclust) {
+ assert(pos == 0);
+ goto set_clusters;
+ }
+
+ /* go to cluster at pos */
+ cur_pos = bytesperclust;
+ while (1) {
+ if (pos <= cur_pos)
+ break;
+ if (IS_LAST_CLUST(curclust, mydata->fatsize))
+ break;
+
+ newclust = get_fatent(mydata, curclust);
+ if (!IS_LAST_CLUST(newclust, mydata->fatsize) &&
+ CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("curclust: 0x%x\n", curclust);
+ debug("Invalid FAT entry\n");
+ return -1;
+ }
+
+ cur_pos += bytesperclust;
+ curclust = newclust;
+ }
+ if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
+ assert(pos == cur_pos);
+ goto set_clusters;
+ }
+
+ assert(pos < cur_pos);
+ cur_pos -= bytesperclust;
+
+ /* overwrite */
+ assert(IS_LAST_CLUST(curclust, mydata->fatsize) ||
+ !CHECK_CLUST(curclust, mydata->fatsize));
+
+ while (1) {
+ /* search for allocated consecutive clusters */
+ actsize = bytesperclust;
+ endclust = curclust;
+ while (1) {
+ if (filesize <= (cur_pos + actsize))
+ break;
+
+ newclust = get_fatent(mydata, endclust);
+
+ if (newclust != endclust + 1)
+ break;
+ if (IS_LAST_CLUST(newclust, mydata->fatsize))
+ break;
+ if (CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("curclust: 0x%x\n", curclust);
+ debug("Invalid FAT entry\n");
+ return -1;
+ }
+
+ actsize += bytesperclust;
+ endclust = newclust;
+ }
+
+ /* overwrite to <curclust..endclust> */
+ if (pos < cur_pos)
+ offset = 0;
+ else
+ offset = pos - cur_pos;
+ wsize = min_t(unsigned long long, actsize, filesize - cur_pos);
+ wsize -= offset;
+
+ if (get_set_cluster(mydata, curclust, offset,
+ buffer, wsize, &actsize)) {
+ printf("Error get-and-setting cluster\n");
+ return -1;
+ }
+ buffer += wsize;
+ *gotsize += wsize;
+ cur_pos += offset + wsize;
+
+ if (filesize <= cur_pos)
+ break;
+
+ if (IS_LAST_CLUST(newclust, mydata->fatsize))
+ /* no more clusters */
+ break;
+
+ curclust = newclust;
+ }
+
+ if (filesize <= cur_pos) {
+ /* no more write */
+ newclust = get_fatent(mydata, endclust);
+ if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
+ /* truncate the rest */
+ clear_fatent(mydata, newclust);
+
+ /* Mark end of file in FAT */
+ if (mydata->fatsize == 12)
+ newclust = 0xfff;
+ else if (mydata->fatsize == 16)
+ newclust = 0xffff;
+ else if (mydata->fatsize == 32)
+ newclust = 0xfffffff;
+ set_fatent_value(mydata, endclust, newclust);
+ }
+
+ return 0;
+ }
+
+ curclust = endclust;
+ filesize -= cur_pos;
+ assert(!do_div(cur_pos, bytesperclust));
+
+set_clusters:
+ /* allocate and write */
+ assert(!pos);
+
+ /* Assure that curclust is valid */
+ if (!curclust) {
+ curclust = find_empty_cluster(mydata);
+ set_start_cluster(mydata, dentptr, curclust);
+ } else {
+ newclust = get_fatent(mydata, curclust);
+
+ if (IS_LAST_CLUST(newclust, mydata->fatsize)) {
+ newclust = determine_fatent(mydata, curclust);
+ set_fatent_value(mydata, curclust, newclust);
+ curclust = newclust;
+ } else {
+ debug("error: something wrong\n");
+ return -1;
+ }
+ }
+
+ /* TODO: already partially written */
+ if (check_overflow(mydata, curclust, filesize)) {
+ printf("Error: no space left: %llu\n", filesize);
+ return -1;
+ }
+
+ actsize = bytesperclust;
+ endclust = curclust;
+ do {
+ /* search for consecutive clusters */
+ while (actsize < filesize) {
+ newclust = determine_fatent(mydata, endclust);
+
+ if ((newclust - 1) != endclust)
+ /* write to <curclust..endclust> */
+ goto getit;
+
+ if (CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("newclust: 0x%x\n", newclust);
+ debug("Invalid FAT entry\n");
+ return 0;
+ }
+ endclust = newclust;
+ actsize += bytesperclust;
+ }
+
+ /* set remaining bytes */
+ actsize = filesize;
+ if (set_cluster(mydata, curclust, buffer, (u32)actsize) != 0) {
+ debug("error: writing cluster\n");
+ return -1;
+ }
+ *gotsize += actsize;
+
+ /* Mark end of file in FAT */
+ if (mydata->fatsize == 12)
+ newclust = 0xfff;
+ else if (mydata->fatsize == 16)
+ newclust = 0xffff;
+ else if (mydata->fatsize == 32)
+ newclust = 0xfffffff;
+ set_fatent_value(mydata, endclust, newclust);
+
+ return 0;
+getit:
+ if (set_cluster(mydata, curclust, buffer, (u32)actsize) != 0) {
+ debug("error: writing cluster\n");
+ return -1;
+ }
+ *gotsize += actsize;
+ filesize -= actsize;
+ buffer += actsize;
+
+ if (CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("newclust: 0x%x\n", newclust);
+ debug("Invalid FAT entry\n");
+ return 0;
+ }
+ actsize = bytesperclust;
+ curclust = endclust = newclust;
+ } while (1);
+
+ return 0;
+}
+
+/**
+ * fill_dentry() - fill directory entry with shortname
+ *
+ * @mydata: private filesystem parameters
+ * @dentptr: directory entry
+ * @shortname: chosen short name
+ * @start_cluster: first cluster of file
+ * @size: file size
+ * @attr: file attributes
+ */
+static void fill_dentry(fsdata *mydata, dir_entry *dentptr,
+ const char *shortname, __u32 start_cluster, __u32 size, __u8 attr)
+{
+ memset(dentptr, 0, sizeof(*dentptr));
+
+ set_start_cluster(mydata, dentptr, start_cluster);
+ dentptr->size = cpu_to_le32(size);
+
+ dentptr->attr = attr;
+
+ memcpy(&dentptr->nameext, shortname, SHORT_NAME_SIZE);
+}
+
+/**
+ * find_directory_entry() - find a directory entry by filename
+ *
+ * @itr: directory iterator
+ * @filename: name of file to find
+ * Return: directory entry or NULL
+ */
+static dir_entry *find_directory_entry(fat_itr *itr, char *filename)
+{
+ int match = 0;
+
+ while (fat_itr_next(itr)) {
+ /* check both long and short name: */
+ if (!strcasecmp(filename, itr->name))
+ match = 1;
+ else if (itr->name != itr->s_name &&
+ !strcasecmp(filename, itr->s_name))
+ match = 1;
+
+ if (!match)
+ continue;
+
+ if (itr->dent->nameext.name[0] == '\0')
+ return NULL;
+ else
+ return itr->dent;
+ }
+
+ return NULL;
+}
+
+static int split_filename(char *filename, char **dirname, char **basename)
+{
+ char *p, *last_slash, *last_slash_cont;
+
+again:
+ p = filename;
+ last_slash = NULL;
+ last_slash_cont = NULL;
+ while (*p) {
+ if (ISDIRDELIM(*p)) {
+ last_slash = p;
+ last_slash_cont = p;
+ /* continuous slashes */
+ while (ISDIRDELIM(*p))
+ last_slash_cont = p++;
+ if (!*p)
+ break;
+ }
+ p++;
+ }
+
+ if (last_slash) {
+ if (last_slash_cont == (filename + strlen(filename) - 1)) {
+ /* remove trailing slashes */
+ *last_slash = '\0';
+ goto again;
+ }
+
+ if (last_slash == filename) {
+ /* avoid ""(null) directory */
+ *dirname = "/";
+ } else {
+ *last_slash = '\0';
+ *dirname = filename;
+ }
+
+ *last_slash_cont = '\0';
+ filename = last_slash_cont + 1;
+ } else {
+ *dirname = "/"; /* root by default */
+ }
+
+ /*
+ * The FAT32 File System Specification v1.03 requires leading and
+ * trailing spaces as well as trailing periods to be ignored.
+ */
+ for (; *filename == ' '; ++filename)
+ ;
+
+ /* Keep special entries '.' and '..' */
+ if (filename[0] == '.' &&
+ (!filename[1] || (filename[1] == '.' && !filename[2])))
+ goto done;
+
+ /* Remove trailing periods and spaces */
+ for (p = filename + strlen(filename) - 1; p >= filename; --p) {
+ switch (*p) {
+ case ' ':
+ case '.':
+ *p = 0;
+ break;
+ default:
+ goto done;
+ }
+ }
+
+done:
+ *basename = filename;
+
+ return 0;
+}
+
+/**
+ * normalize_longname() - check long file name and convert to lower case
+ *
+ * We assume here that the FAT file system is using an 8bit code page.
+ * Linux typically uses CP437, EDK2 assumes CP1250.
+ *
+ * @l_filename: preallocated buffer receiving the normalized name
+ * @filename: filename to normalize
+ * Return: 0 on success, -1 on failure
+ */
+static int normalize_longname(char *l_filename, const char *filename)
+{
+ const char *p, illegal[] = "<>:\"/\\|?*";
+ size_t len;
+
+ len = strlen(filename);
+ if (!len || len >= VFAT_MAXLEN_BYTES || filename[len - 1] == '.')
+ return -1;
+
+ for (p = filename; *p; ++p) {
+ if ((unsigned char)*p < 0x20)
+ return -1;
+ if (strchr(illegal, *p))
+ return -1;
+ }
+
+ strcpy(l_filename, filename);
+ downcase(l_filename, VFAT_MAXLEN_BYTES);
+
+ return 0;
+}
+
+int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
+ loff_t size, loff_t *actwrite)
+{
+ dir_entry *retdent;
+ fsdata datablock = { .fatbuf = NULL, };
+ fsdata *mydata = &datablock;
+ fat_itr *itr = NULL;
+ int ret = -1;
+ char *filename_copy, *parent, *basename;
+ char l_filename[VFAT_MAXLEN_BYTES];
+
+ debug("writing %s\n", filename);
+
+ filename_copy = strdup(filename);
+ if (!filename_copy)
+ return -ENOMEM;
+
+ split_filename(filename_copy, &parent, &basename);
+ if (!strlen(basename)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (normalize_longname(l_filename, basename)) {
+ printf("FAT: illegal filename (%s)\n", basename);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ itr = malloc_cache_aligned(sizeof(fat_itr));
+ if (!itr) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ ret = fat_itr_root(itr, &datablock);
+ if (ret)
+ goto exit;
+
+ total_sector = datablock.total_sect;
+
+ ret = fat_itr_resolve(itr, parent, TYPE_DIR);
+ if (ret) {
+ printf("%s: doesn't exist (%d)\n", parent, ret);
+ goto exit;
+ }
+
+ retdent = find_directory_entry(itr, l_filename);
+
+ if (retdent) {
+ if (fat_itr_isdir(itr)) {
+ ret = -EISDIR;
+ goto exit;
+ }
+
+ /* A file exists */
+ if (pos == -1)
+ /* Append to the end */
+ pos = FAT2CPU32(retdent->size);
+ if (pos > retdent->size) {
+ /* No hole allowed */
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Update file size in a directory entry */
+ retdent->size = cpu_to_le32(pos + size);
+ } else {
+ /* Create a new file */
+ char shortname[SHORT_NAME_SIZE];
+ int ndent;
+
+ if (pos) {
+ /* No hole allowed */
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Check if long name is needed */
+ ndent = set_name(itr, basename, shortname);
+ if (ndent < 0) {
+ ret = ndent;
+ goto exit;
+ }
+ ret = fat_find_empty_dentries(itr, ndent);
+ if (ret)
+ goto exit;
+ if (ndent > 1) {
+ /* Set long name entries */
+ ret = fill_dir_slot(itr, basename, shortname);
+ if (ret)
+ goto exit;
+ }
+
+ /* Set short name entry */
+ fill_dentry(itr->fsdata, itr->dent, shortname, 0, size,
+ ATTR_ARCH);
+
+ retdent = itr->dent;
+ }
+
+ ret = set_contents(mydata, retdent, pos, buffer, size, actwrite);
+ if (ret < 0) {
+ printf("Error: writing contents\n");
+ ret = -EIO;
+ goto exit;
+ }
+ debug("attempt to write 0x%llx bytes\n", *actwrite);
+
+ /* Flush fat buffer */
+ ret = flush_dirty_fat_buffer(mydata);
+ if (ret) {
+ printf("Error: flush fat buffer\n");
+ ret = -EIO;
+ goto exit;
+ }
+
+ /* Write directory table to device */
+ ret = flush_dir(itr);
+
+exit:
+ free(filename_copy);
+ free(mydata->fatbuf);
+ free(itr);
+ return ret;
+}
+
+int file_fat_write(const char *filename, void *buffer, loff_t offset,
+ loff_t maxsize, loff_t *actwrite)
+{
+ return file_fat_write_at(filename, offset, buffer, maxsize, actwrite);
+}
+
+static int fat_dir_entries(fat_itr *itr)
+{
+ fat_itr *dirs;
+ fsdata fsdata = { .fatbuf = NULL, }, *mydata = &fsdata;
+ /* for FATBUFSIZE */
+ int count;
+
+ dirs = malloc_cache_aligned(sizeof(fat_itr));
+ if (!dirs) {
+ debug("Error: allocating memory\n");
+ count = -ENOMEM;
+ goto exit;
+ }
+
+ /* duplicate fsdata */
+ fat_itr_child(dirs, itr);
+ fsdata = *dirs->fsdata;
+
+ /* allocate local fat buffer */
+ fsdata.fatbuf = malloc_cache_aligned(FATBUFSIZE);
+ if (!fsdata.fatbuf) {
+ debug("Error: allocating memory\n");
+ count = -ENOMEM;
+ goto exit;
+ }
+ fsdata.fatbufnum = -1;
+ dirs->fsdata = &fsdata;
+
+ for (count = 0; fat_itr_next(dirs); count++)
+ ;
+
+exit:
+ free(fsdata.fatbuf);
+ free(dirs);
+ return count;
+}
+
+/**
+ * delete_single_dentry() - delete a single directory entry
+ *
+ * @itr: directory iterator
+ * Return: 0 for success
+ */
+static int delete_single_dentry(fat_itr *itr)
+{
+ struct dir_entry *dent = itr->dent;
+
+ memset(dent, 0, sizeof(*dent));
+ dent->nameext.name[0] = DELETED_FLAG;
+
+ if (!itr->remaining)
+ return flush_dir(itr);
+ return 0;
+}
+
+/**
+ * delete_long_name() - delete long name directory entries
+ *
+ * @itr: directory iterator
+ * Return: 0 for success
+ */
+static int delete_long_name(fat_itr *itr)
+{
+ int seqn = itr->dent->nameext.name[0] & ~LAST_LONG_ENTRY_MASK;
+
+ while (seqn--) {
+ struct dir_entry *dent;
+ int ret;
+
+ ret = delete_single_dentry(itr);
+ if (ret)
+ return ret;
+ dent = next_dent(itr);
+ if (!dent)
+ return -EIO;
+ }
+ return 0;
+}
+
+/**
+ * delete_dentry_long() - remove directory entry
+ *
+ * @itr: directory iterator
+ * Return: 0 for success
+ */
+static int delete_dentry_long(fat_itr *itr)
+{
+ fsdata *mydata = itr->fsdata;
+ dir_entry *dent = itr->dent;
+
+ /* free cluster blocks */
+ clear_fatent(mydata, START(dent));
+ if (flush_dirty_fat_buffer(mydata) < 0) {
+ printf("Error: flush fat buffer\n");
+ return -EIO;
+ }
+ /* Position to first directory entry for long name */
+ if (itr->clust != itr->dent_clust) {
+ int ret;
+
+ ret = fat_move_to_cluster(itr, itr->dent_clust);
+ if (ret)
+ return ret;
+ }
+ itr->dent = itr->dent_start;
+ itr->remaining = itr->dent_rem;
+ dent = itr->dent_start;
+ /* Delete long name */
+ if ((dent->attr & ATTR_VFAT) == ATTR_VFAT &&
+ (dent->nameext.name[0] & LAST_LONG_ENTRY_MASK)) {
+ int ret;
+
+ ret = delete_long_name(itr);
+ if (ret)
+ return ret;
+ }
+ /* Delete short name */
+ delete_single_dentry(itr);
+ return flush_dir(itr);
+}
+
+int fat_unlink(const char *filename)
+{
+ fsdata fsdata = { .fatbuf = NULL, };
+ fat_itr *itr = NULL;
+ int n_entries, ret;
+ char *filename_copy, *dirname, *basename;
+
+ filename_copy = strdup(filename);
+ if (!filename_copy) {
+ printf("Error: allocating memory\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ split_filename(filename_copy, &dirname, &basename);
+
+ if (!strcmp(dirname, "/") && !strcmp(basename, "")) {
+ printf("Error: cannot remove root\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ itr = malloc_cache_aligned(sizeof(fat_itr));
+ if (!itr) {
+ printf("Error: allocating memory\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ ret = fat_itr_root(itr, &fsdata);
+ if (ret)
+ goto exit;
+
+ total_sector = fsdata.total_sect;
+
+ ret = fat_itr_resolve(itr, dirname, TYPE_DIR);
+ if (ret) {
+ printf("%s: doesn't exist (%d)\n", dirname, ret);
+ ret = -ENOENT;
+ goto exit;
+ }
+
+ if (!find_directory_entry(itr, basename)) {
+ printf("%s: doesn't exist\n", basename);
+ ret = -ENOENT;
+ goto exit;
+ }
+
+ if (fat_itr_isdir(itr)) {
+ n_entries = fat_dir_entries(itr);
+ if (n_entries < 0) {
+ ret = n_entries;
+ goto exit;
+ }
+ if (n_entries > 2) {
+ printf("Error: directory is not empty: %d\n",
+ n_entries);
+ ret = -EINVAL;
+ goto exit;
+ }
+ }
+
+ ret = delete_dentry_long(itr);
+
+exit:
+ free(fsdata.fatbuf);
+ free(itr);
+ free(filename_copy);
+
+ return ret;
+}
+
+int fat_mkdir(const char *dirname)
+{
+ dir_entry *retdent;
+ fsdata datablock = { .fatbuf = NULL, };
+ fsdata *mydata = &datablock;
+ fat_itr *itr = NULL;
+ char *dirname_copy, *parent, *basename;
+ char l_dirname[VFAT_MAXLEN_BYTES];
+ int ret = -1;
+ loff_t actwrite;
+ unsigned int bytesperclust;
+ dir_entry *dotdent = NULL;
+
+ dirname_copy = strdup(dirname);
+ if (!dirname_copy)
+ goto exit;
+
+ split_filename(dirname_copy, &parent, &basename);
+ if (!strlen(basename)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (normalize_longname(l_dirname, basename)) {
+ printf("FAT: illegal filename (%s)\n", basename);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ itr = malloc_cache_aligned(sizeof(fat_itr));
+ if (!itr) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ ret = fat_itr_root(itr, &datablock);
+ if (ret)
+ goto exit;
+
+ total_sector = datablock.total_sect;
+
+ ret = fat_itr_resolve(itr, parent, TYPE_DIR);
+ if (ret) {
+ printf("%s: doesn't exist (%d)\n", parent, ret);
+ goto exit;
+ }
+
+ retdent = find_directory_entry(itr, l_dirname);
+
+ if (retdent) {
+ printf("%s: already exists\n", l_dirname);
+ ret = -EEXIST;
+ goto exit;
+ } else {
+ char shortname[SHORT_NAME_SIZE];
+ int ndent;
+
+ if (itr->is_root) {
+ /* root dir cannot have "." or ".." */
+ if (!strcmp(l_dirname, ".") ||
+ !strcmp(l_dirname, "..")) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ }
+
+ /* Check if long name is needed */
+ ndent = set_name(itr, basename, shortname);
+ if (ndent < 0) {
+ ret = ndent;
+ goto exit;
+ }
+ ret = fat_find_empty_dentries(itr, ndent);
+ if (ret)
+ goto exit;
+ if (ndent > 1) {
+ /* Set long name entries */
+ ret = fill_dir_slot(itr, basename, shortname);
+ if (ret)
+ goto exit;
+ }
+
+ /* Set attribute as archive for regular file */
+ fill_dentry(itr->fsdata, itr->dent, shortname, 0, 0,
+ ATTR_DIR | ATTR_ARCH);
+
+ retdent = itr->dent;
+ }
+
+ /* Default entries */
+ bytesperclust = mydata->clust_size * mydata->sect_size;
+ dotdent = malloc_cache_aligned(bytesperclust);
+ if (!dotdent) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ memset(dotdent, 0, bytesperclust);
+
+ memcpy(&dotdent[0].nameext, ". ", 11);
+ dotdent[0].attr = ATTR_DIR | ATTR_ARCH;
+
+ memcpy(&dotdent[1].nameext, ".. ", 11);
+ dotdent[1].attr = ATTR_DIR | ATTR_ARCH;
+
+ if (itr->is_root)
+ set_start_cluster(mydata, &dotdent[1], 0);
+ else
+ set_start_cluster(mydata, &dotdent[1], itr->start_clust);
+
+ ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent,
+ bytesperclust, &actwrite);
+ if (ret < 0) {
+ printf("Error: writing contents\n");
+ goto exit;
+ }
+ /* Write twice for "." */
+ set_start_cluster(mydata, &dotdent[0], START(retdent));
+ ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent,
+ bytesperclust, &actwrite);
+ if (ret < 0) {
+ printf("Error: writing contents\n");
+ goto exit;
+ }
+
+ /* Flush fat buffer */
+ ret = flush_dirty_fat_buffer(mydata);
+ if (ret) {
+ printf("Error: flush fat buffer\n");
+ ret = -EIO;
+ goto exit;
+ }
+
+ /* Write directory table to device */
+ ret = flush_dir(itr);
+
+exit:
+ free(dirname_copy);
+ free(mydata->fatbuf);
+ free(itr);
+ free(dotdent);
+ return ret;
+}