fs: fat: update parent dirs metadata on dentry create/delete

POSIX filesystem functions that create or remove directory entries contain
text along the lines of "[function] shall mark for update the last data
modification and last file status change timestamps of the parent
directory of each file." [1][2][3] The common theme is these timestamp
updates occur when a directory entry is added or removed. The
create_link() and delete_dentry_link() functions have been changed to
update the modification timestamp on the directory where the direntry
change occurs. This differs slightly from Linux in the case of rename(),
where Linux will not update `new_path`'s parent directory's timestamp if
it is replacing an existing file. (via `vfat_add_entry` [4])

The timestamps are not updated if the build configuration does not support
RTCs. This is an effort to minimize introducing erratic timestamps where
they would go from [current date] -> 2000-01-01 (error timestamp in the
FAT driver). I would assume an unchanged timestamp would be more valuable
than a default timestamp in these cases.

[1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/rename.html
[2] https://pubs.opengroup.org/onlinepubs/9799919799/functions/unlink.html
[3] https://pubs.opengroup.org/onlinepubs/9799919799/functions/open.html
[4] https://elixir.bootlin.com/linux/v6.12.6/source/fs/fat/namei_vfat.c#L682

Signed-off-by: Gabriel Dalimonte <gabriel.dalimonte@gmail.com>
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index d4952e2..0b92454 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -1238,6 +1238,64 @@
 }
 
 /**
+ * update_parent_dir_props - updates the modified time for the parent directory
+ *
+ * @dir_itr:	iterator positioned anywhere in a directory whose parent
+ * should be updated
+ * @Return:	0 for success, -errno otherwise
+ */
+static int update_parent_dir_props(fat_itr *dir_itr)
+{
+	int ret = 0;
+
+	fat_itr itr;
+	fsdata fsdata = { .fatbuf = NULL, }, *mydata = &fsdata;
+	__u32 target_clust = dir_itr->start_clust;
+
+	/* Short circuit if no RTC because it only updates timestamps */
+	if (!CONFIG_IS_ENABLED(DM_RTC))
+		return ret;
+
+	/* duplicate fsdata */
+	itr = *dir_itr;
+	fsdata = *itr.fsdata;
+
+	/* allocate local fat buffer */
+	fsdata.fatbuf = malloc_cache_aligned(FATBUFSIZE);
+	if (!fsdata.fatbuf) {
+		log_debug("Error: allocating memory\n");
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	fsdata.fatbufnum = -1;
+	itr.fsdata = &fsdata;
+
+	if (!itr.is_root) {
+		ret = fat_itr_parent(&itr);
+		if (ret)
+			goto exit;
+
+		while (fat_itr_next(&itr)) {
+			if (START(itr.dent) == target_clust)
+				goto update;
+		}
+
+		/* dent not found */
+		ret = -EIO;
+		goto exit;
+update:
+		dentry_set_time(itr.dent);
+		ret = flush_dir(&itr);
+	}
+
+exit:
+	free(fsdata.fatbuf);
+
+	return ret;
+}
+
+/**
  * create_link() - inserts a directory entry for a file or directory
  *
  * @itr:	directory iterator
@@ -1270,8 +1328,9 @@
 	}
 
 	fill_dentry(itr->fsdata, itr->dent, shortname, clust, size, attr);
+	ret = update_parent_dir_props(itr);
 
-	return 0;
+	return ret;
 }
 
 /**
@@ -1612,20 +1671,25 @@
  */
 static int delete_dentry_link(fat_itr *itr)
 {
+	int ret;
+
 	itr->dent = itr->dent_start;
 	itr->remaining = itr->dent_rem;
 	/* Delete long name */
 	if ((itr->dent->attr & ATTR_VFAT) == ATTR_VFAT &&
 	    (itr->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);
+
+	ret = flush_dir(itr);
+	if (ret)
+		return ret;
+
+	return update_parent_dir_props(itr);
 }
 
 /**
@@ -2047,6 +2111,10 @@
 		*new_itr->dent = *old_itr->dent;
 		new_itr->dent->nameext = new_name;
 		new_itr->dent->lcase = lcase;
+
+		ret = update_parent_dir_props(new_itr);
+		if (ret)
+			goto exit;
 	} else {
 		/* reset iterator to the start of the directory */
 		ret = fat_move_to_cluster(new_itr, new_itr->start_clust);
@@ -2089,6 +2157,8 @@
 		ret = flush_dir(new_itr);
 		if (ret)
 			goto exit;
+		/* restore directory location to update parent props below */
+		fat_itr_child(new_itr, new_itr);
 	}
 
 	/* refresh old in case write happened to the same block. */