tune: label: fix bitmap entry corruption when adding new volume label

When adding new label using tune.exfat or exfatlabel against exfat
device formatted by some camera vendor, bitmap entry in root entry is
corrupted by overwriting. the format utils of vendor doesn't add volume
entry. tune and exfatlabel of exfatprogs assumes that the first entry of
the root entry is a volume entry and try overwrite it. This patch lookup
and updates the volume entry in the root entry. And adds a new entry to
an empty slot if it doesn't exist.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c
index fd176e0..81e86d2 100644
--- a/exfat2img/exfat2img.c
+++ b/exfat2img/exfat2img.c
@@ -154,50 +154,6 @@
 	return err;
 }
 
-static int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs)
-{
-	struct pbr *pbr;
-	int err = 0;
-	unsigned int sect_size, clu_size;
-
-	pbr = malloc(sizeof(struct pbr));
-
-	if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) !=
-	    (ssize_t)sizeof(*pbr)) {
-		exfat_err("failed to read a boot sector\n");
-		err = -EIO;
-		goto err;
-	}
-
-	err = -EINVAL;
-	if (memcmp(pbr->bpb.oem_name, "EXFAT   ", 8) != 0) {
-		exfat_err("failed to find exfat file system\n");
-		goto err;
-	}
-
-	sect_size = 1 << pbr->bsx.sect_size_bits;
-	clu_size = 1 << (pbr->bsx.sect_size_bits +
-			 pbr->bsx.sect_per_clus_bits);
-
-	if (sect_size < 512 || sect_size > 4 * KB) {
-		exfat_err("too small or big sector size: %d\n",
-			  sect_size);
-		goto err;
-	}
-
-	if (clu_size < sect_size || clu_size > 32 * MB) {
-		exfat_err("too small or big cluster size: %d\n",
-			  clu_size);
-		goto err;
-	}
-
-	*bs = pbr;
-	return 0;
-err:
-	free(pbr);
-	return err;
-}
-
 /**
  * @end: excluded.
  */
diff --git a/fsck/fsck.c b/fsck/fsck.c
index 52b3f1b..06cb129 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -808,45 +808,6 @@
 	return ret;
 }
 
-static int read_volume_label(struct exfat *exfat)
-{
-	struct exfat_dentry *dentry;
-	int err;
-	__le16 disk_label[VOLUME_LABEL_MAX_LEN];
-	struct exfat_lookup_filter filter = {
-		.in.type = EXFAT_VOLUME,
-		.in.filter = NULL,
-	};
-
-	err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
-	if (err)
-		return err;
-
-	dentry = filter.out.dentry_set;
-
-	if (dentry->vol_char_cnt == 0)
-		goto out;
-
-	if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) {
-		exfat_err("too long label. %d\n", dentry->vol_char_cnt);
-		err = -EINVAL;
-		goto out;
-	}
-
-	memcpy(disk_label, dentry->vol_label, sizeof(disk_label));
-	if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2,
-		exfat->volume_label, sizeof(exfat->volume_label)) < 0) {
-		exfat_err("failed to decode volume label\n");
-		err = -EINVAL;
-		goto out;
-	}
-
-	exfat_info("volume label [%s]\n", exfat->volume_label);
-out:
-	free(filter.out.dentry_set);
-	return err;
-}
-
 static int read_bitmap(struct exfat *exfat)
 {
 	struct exfat_lookup_filter filter = {
@@ -1202,7 +1163,7 @@
 	exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n",
 		root->first_clus, root->size);
 
-	if (read_volume_label(exfat))
+	if (exfat_read_volume_label(exfat))
 		exfat_err("failed to read volume label\n");
 
 	err = read_bitmap(exfat);
diff --git a/include/libexfat.h b/include/libexfat.h
index b98f2b6..0623501 100644
--- a/include/libexfat.h
+++ b/include/libexfat.h
@@ -145,9 +145,8 @@
 ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
 			char *out_str, size_t out_size);
 off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd);
-int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off);
-int exfat_set_volume_label(struct exfat_blk_dev *bd,
-		char *label_input, off_t root_clu_off);
+int exfat_read_volume_label(struct exfat *exfat);
+int exfat_set_volume_label(struct exfat *exfat, char *label_input);
 int exfat_read_sector(struct exfat_blk_dev *bd, void *buf,
 		unsigned int sec_off);
 int exfat_write_sector(struct exfat_blk_dev *bd, void *buf,
@@ -169,6 +168,8 @@
 int exfat_o2c(struct exfat *exfat, off_t device_offset,
 	      unsigned int *clu, unsigned int *offset);
 bool exfat_heap_clus(struct exfat *exfat, clus_t clus);
+int exfat_root_clus_count(struct exfat *exfat);
+int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs);
 
 /*
  * Exfat Print
diff --git a/label/label.c b/label/label.c
index b41e827..8cd5748 100644
--- a/label/label.c
+++ b/label/label.c
@@ -13,6 +13,7 @@
 
 #include "exfat_ondisk.h"
 #include "libexfat.h"
+#include "exfat_fs.h"
 
 static void usage(void)
 {
@@ -39,7 +40,6 @@
 	struct exfat_blk_dev bd;
 	struct exfat_user_input ui;
 	bool version_only = false;
-	off_t root_clu_off;
 	int serial_mode = 0;
 	int flags = 0;
 
@@ -96,15 +96,43 @@
 			ret = exfat_set_volume_serial(&bd, &ui);
 		}
 	} else {
-		/* Mode to change or display volume label */
-		root_clu_off = exfat_get_root_entry_offset(&bd);
-		if (root_clu_off < 0)
+		struct exfat *exfat;
+		struct pbr *bs;
+
+		ret = read_boot_sect(&bd, &bs);
+		if (ret)
 			goto close_fd_out;
 
+		exfat = exfat_alloc_exfat(&bd, bs);
+		if (!exfat) {
+			free(bs);
+			ret = -ENOMEM;
+			goto close_fd_out;
+		}
+
+		exfat->root = exfat_alloc_inode(ATTR_SUBDIR);
+		if (!exfat->root) {
+			ret = -ENOMEM;
+			goto free_exfat;
+		}
+
+		exfat->root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
+		if (exfat_root_clus_count(exfat)) {
+			exfat_err("failed to follow the cluster chain of root\n");
+			exfat_free_inode(exfat->root);
+			ret = -EINVAL;
+			goto free_exfat;
+		}
+
+		/* Mode to change or display volume label */
 		if (flags == EXFAT_GET_VOLUME_LABEL)
-			ret = exfat_show_volume_label(&bd, root_clu_off);
+			ret = exfat_read_volume_label(exfat);
 		else if (flags == EXFAT_SET_VOLUME_LABEL)
-			ret = exfat_set_volume_label(&bd, argv[2], root_clu_off);
+			ret = exfat_set_volume_label(exfat, argv[2]);
+
+free_exfat:
+		if (exfat)
+			exfat_free_exfat(exfat);
 	}
 
 close_fd_out:
diff --git a/lib/libexfat.c b/lib/libexfat.c
index 92e5e91..d7c1df1 100644
--- a/lib/libexfat.c
+++ b/lib/libexfat.c
@@ -20,6 +20,7 @@
 #include "libexfat.h"
 #include "version.h"
 #include "exfat_fs.h"
+#include "exfat_dir.h"
 
 unsigned int print_level  = EXFAT_INFO;
 
@@ -403,74 +404,91 @@
 	return volume_label;
 }
 
-int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off)
+int exfat_read_volume_label(struct exfat *exfat)
 {
-	struct exfat_dentry *vol_entry;
-	char *volume_label;
-	int nbytes;
+	struct exfat_dentry *dentry;
+	int err;
+	__le16 disk_label[VOLUME_LABEL_MAX_LEN];
+	struct exfat_lookup_filter filter = {
+		.in.type = EXFAT_VOLUME,
+		.in.filter = NULL,
+	};
 
-	vol_entry = malloc(sizeof(struct exfat_dentry));
-	if (!vol_entry) {
-		exfat_err("failed to allocate memory\n");
-		return -ENOMEM;
+	err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+	if (err)
+		return err;
+
+	dentry = filter.out.dentry_set;
+
+	if (dentry->vol_char_cnt == 0)
+		goto out;
+
+	if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) {
+		exfat_err("too long label. %d\n", dentry->vol_char_cnt);
+		err = -EINVAL;
+		goto out;
 	}
 
-	nbytes = exfat_read(bd->dev_fd, vol_entry,
-		sizeof(struct exfat_dentry), root_clu_off);
-	if (nbytes != sizeof(struct exfat_dentry)) {
-		exfat_err("volume entry read failed: %d\n", errno);
-		free(vol_entry);
-		return -1;
+	memcpy(disk_label, dentry->vol_label, sizeof(disk_label));
+	if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2,
+		exfat->volume_label, sizeof(exfat->volume_label)) < 0) {
+		exfat_err("failed to decode volume label\n");
+		err = -EINVAL;
+		goto out;
 	}
 
-	volume_label = exfat_conv_volume_label(vol_entry);
-	if (!volume_label) {
-		free(vol_entry);
-		return -EINVAL;
-	}
-
-	exfat_info("label: %s\n", volume_label);
-
-	free(volume_label);
-	free(vol_entry);
-	return 0;
+	exfat_info("label: %s\n", exfat->volume_label);
+out:
+	free(filter.out.dentry_set);
+	return err;
 }
 
-int exfat_set_volume_label(struct exfat_blk_dev *bd,
-		char *label_input, off_t root_clu_off)
+int exfat_set_volume_label(struct exfat *exfat, char *label_input)
 {
-	struct exfat_dentry vol;
-	int nbytes;
+	struct exfat_dentry *pvol;
+	struct exfat_dentry_loc loc;
 	__u16 volume_label[VOLUME_LABEL_MAX_LEN];
-	int volume_label_len;
+	int volume_label_len, dcount, err;
+
+	struct exfat_lookup_filter filter = {
+		.in.type = EXFAT_VOLUME,
+		.in.filter = NULL,
+	};
+
+	err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+	if (!err) {
+		pvol = filter.out.dentry_set;
+		dcount = filter.out.dentry_count;
+		memset(pvol->vol_label, 0, sizeof(pvol->vol_label));
+	} else {
+		pvol = calloc(sizeof(struct exfat_dentry), 1);
+		if (!pvol)
+			return -ENOMEM;
+
+		dcount = 1;
+		pvol->type = EXFAT_VOLUME;
+	}
 
 	volume_label_len = exfat_utf16_enc(label_input,
 			volume_label, sizeof(volume_label));
 	if (volume_label_len < 0) {
 		exfat_err("failed to encode volume label\n");
+		free(pvol);
 		return -1;
 	}
 
-	vol.type = EXFAT_VOLUME;
-	memset(vol.vol_label, 0, sizeof(vol.vol_label));
-	memcpy(vol.vol_label, volume_label, volume_label_len);
-	vol.vol_char_cnt = volume_label_len/2;
+	memcpy(pvol->vol_label, volume_label, volume_label_len);
+	pvol->vol_char_cnt = volume_label_len/2;
 
-	nbytes = exfat_write(bd->dev_fd, &vol, sizeof(struct exfat_dentry),
-			root_clu_off);
-	if (nbytes != sizeof(struct exfat_dentry)) {
-		exfat_err("volume entry write failed: %d\n", errno);
-		return -1;
-	}
-
-	if (fsync(bd->dev_fd) == -1) {
-		exfat_err("failed to sync volume entry: %d, %s\n", errno,
-			  strerror(errno));
-		return -1;
-	}
-
+	loc.parent = exfat->root;
+	loc.file_offset = filter.out.file_offset;
+	loc.dev_offset = filter.out.dev_offset;
+	err = exfat_add_dentry_set(exfat, &loc, pvol, dcount, false);
 	exfat_info("new label: %s\n", label_input);
-	return 0;
+
+	free(pvol);
+
+	return err;
 }
 
 int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off)
@@ -760,3 +778,79 @@
 	return clus >= EXFAT_FIRST_CLUSTER &&
 		(clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count;
 }
+
+int exfat_root_clus_count(struct exfat *exfat)
+{
+	struct exfat_inode *node = exfat->root;
+	clus_t clus, next;
+	int clus_count = 0;
+
+	if (!exfat_heap_clus(exfat, node->first_clus))
+		return -EIO;
+
+	clus = node->first_clus;
+	do {
+		if (exfat_bitmap_get(exfat->alloc_bitmap, clus))
+			return -EINVAL;
+
+		exfat_bitmap_set(exfat->alloc_bitmap, clus);
+
+		if (exfat_get_inode_next_clus(exfat, node, clus, &next)) {
+			exfat_err("ERROR: failed to read the fat entry of root");
+			return -EIO;
+		}
+
+		if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next))
+			return -EINVAL;
+
+		clus = next;
+		clus_count++;
+	} while (clus != EXFAT_EOF_CLUSTER);
+
+	node->size = clus_count * exfat->clus_size;
+	return 0;
+}
+
+int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs)
+{
+	struct pbr *pbr;
+	int err = 0;
+	unsigned int sect_size, clu_size;
+
+	pbr = malloc(sizeof(struct pbr));
+
+	if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) !=
+	    (ssize_t)sizeof(*pbr)) {
+		exfat_err("failed to read a boot sector\n");
+		err = -EIO;
+		goto err;
+	}
+
+	err = -EINVAL;
+	if (memcmp(pbr->bpb.oem_name, "EXFAT   ", 8) != 0) {
+		exfat_err("failed to find exfat file system\n");
+		goto err;
+	}
+
+	sect_size = 1 << pbr->bsx.sect_size_bits;
+	clu_size = 1 << (pbr->bsx.sect_size_bits +
+			 pbr->bsx.sect_per_clus_bits);
+
+	if (sect_size < 512 || sect_size > 4 * KB) {
+		exfat_err("too small or big sector size: %d\n",
+			  sect_size);
+		goto err;
+	}
+
+	if (clu_size < sect_size || clu_size > 32 * MB) {
+		exfat_err("too small or big cluster size: %d\n",
+			  clu_size);
+		goto err;
+	}
+
+	*bs = pbr;
+	return 0;
+err:
+	free(pbr);
+	return err;
+}
diff --git a/tune/tune.c b/tune/tune.c
index a53be59..135f624 100644
--- a/tune/tune.c
+++ b/tune/tune.c
@@ -13,6 +13,7 @@
 
 #include "exfat_ondisk.h"
 #include "libexfat.h"
+#include "exfat_fs.h"
 
 static void usage(void)
 {
@@ -49,7 +50,8 @@
 	bool version_only = false;
 	int flags = 0;
 	char label_input[VOLUME_LABEL_BUFFER_SIZE];
-	off_t root_clu_off;
+	struct exfat *exfat = NULL;
+	struct pbr *bs;
 
 	init_user_input(&ui);
 
@@ -109,16 +111,39 @@
 		goto close_fd_out;
 	}
 
-	root_clu_off = exfat_get_root_entry_offset(&bd);
-	if (root_clu_off < 0)
+	ret = read_boot_sect(&bd, &bs);
+	if (ret)
 		goto close_fd_out;
 
+	exfat = exfat_alloc_exfat(&bd, bs);
+	if (!exfat) {
+		free(bs);
+		ret = -ENOMEM;
+		goto close_fd_out;
+	}
+
+	exfat->root = exfat_alloc_inode(ATTR_SUBDIR);
+	if (!exfat->root) {
+		ret = -ENOMEM;
+		goto close_fd_out;
+	}
+
+	exfat->root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
+	if (exfat_root_clus_count(exfat)) {
+		exfat_err("failed to follow the cluster chain of root\n");
+		exfat_free_inode(exfat->root);
+		ret = -EINVAL;
+		goto close_fd_out;
+	}
+
 	if (flags == EXFAT_GET_VOLUME_LABEL)
-		ret = exfat_show_volume_label(&bd, root_clu_off);
+		ret = exfat_read_volume_label(exfat);
 	else if (flags == EXFAT_SET_VOLUME_LABEL)
-		ret = exfat_set_volume_label(&bd, label_input, root_clu_off);
+		ret = exfat_set_volume_label(exfat, label_input);
 close_fd_out:
 	close(bd.dev_fd);
+	if (exfat)
+		exfat_free_exfat(exfat);
 out:
 	return ret;
 }