Checking for file modifications in Zephyr File System

Simple question with an unusually elusive answer:
While using zephyrs File System service (Docs) is there any way to check if/when a user has modified a file?

===== Environment =====

nRF Connect SDK v2.5.0 (though not opposed to upgrading to one of the 2.6.X releases if helpful)

Designing for nRF52840 (Prototyping on nRF52840DK), Development on Xiao BLE Sense

===== Overview =====

I am using the USB Mass Storage sample app configured for FATfs to read data out of a 16KB json.  Currently I can read and process data but would like to update by rereading and reprocessing data when the json is updated by the user.

Is there anywhere I could find documentation for setting up a callback function on modification or update/polling of the modification timestamp?  If possible, I would like to avoid setting up a separate thread to continuously reread the file's contents.

What is the best way forward?

  • Hello,

    perhaps this could be useful for you. Is this what you are asking for?

  • The fs_mgmt and mgmt/callbacks.h libraries describe the behavior that I'm looking for; however, after implementing a callback object (as per MCUmgr Callbacks docs) to trigger on MGMT_EVT_OP_FS_MGMT_FILE_ACCESS, my callback function never seems to run.

    In the same MCUmgr Callbacks docs I found this callback event described 
    "... the MGMT_EVT_OP_FS_MGMT_FILE_ACCESS event, which will be called after a fs_mgmt file read/write command has been received..."
    This particular phrasing of "a fs_mgmt file read/write command" concerns me that it does not apply to user access through the mounted usb file system and may only apply to MCUmgr specific implementations (such as SMP).

    Is MCUmgr the right tool for detecting user modifications to a usb filesystem?  If so, what setup have I missed in enabling this (see code below)?  If not what tools can accomplish this?

    If there is simply something that I missed in setting up the callback struct (lines 159-168, 183-186) or enabling the file access event, I have included my main.c and prj.conf from the Mass Storage sample:

    #include <stdio.h>
    #include <string.h>
    
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/logging/log.h>
    #include <zephyr/usb/usb_device.h>
    #include <zephyr/usb/usbd.h>
    #include <zephyr/usb/class/usbd_msc.h>
    #include <zephyr/fs/fs.h>
    #include <zephyr/storage/flash_map.h>
    #include <ff.h>
    
    #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
    #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
    #include <zephyr/mgmt/mcumgr/grp/fs_mgmt/fs_mgmt.h>
    #include <zephyr/mgmt/mcumgr/grp/fs_mgmt/fs_mgmt_callbacks.h>
    
    #include <cjson.h>
    
    LOG_MODULE_REGISTER(main);
    
    
    #define STORAGE_PARTITION		storage_partition
    #define STORAGE_PARTITION_ID		FIXED_PARTITION_ID(STORAGE_PARTITION)
    
    static struct fs_mount_t fs_mnt;
    static uint32_t sleep_time_ms[1] = {1000};
    static uint32_t sleep_time_buf[1] = {1000};
    #define READ_PATH "/NAND:/text.txt"
    
    static int setup_flash(struct fs_mount_t *mnt)
    {
    	int rc = 0;
    #if CONFIG_DISK_DRIVER_FLASH
    	unsigned int id;
    	const struct flash_area *pfa;
    
    	mnt->storage_dev = (void *)STORAGE_PARTITION_ID;
    	id = STORAGE_PARTITION_ID;
    
    	rc = flash_area_open(id, &pfa);
    	printk("Area %u at 0x%x on %s for %u bytes\n",
    	       id, (unsigned int)pfa->fa_off, pfa->fa_dev->name,
    	       (unsigned int)pfa->fa_size);
    	
    	printk("RC: %i", rc);
    	
    	if (rc < 0 && IS_ENABLED(CONFIG_APP_WIPE_STORAGE)) {
    		printk("Erasing flash area ... ");
    		rc = flash_area_erase(pfa, 0, pfa->fa_size);
    		printk("%d\n", rc);
    	}
    
    	if (rc < 0) {
    		flash_area_close(pfa);
    	}
    #endif
    	return rc;
    }
    
    static int mount_app_fs(struct fs_mount_t *mnt)
    {
    	int rc;
    
    #if CONFIG_FAT_FILESYSTEM_ELM
    	static FATFS fat_fs;
    
    	mnt->type = FS_FATFS;
    	mnt->fs_data = &fat_fs;
    	if (IS_ENABLED(CONFIG_DISK_DRIVER_RAM)) {
    		mnt->mnt_point = "/RAM:";
    	} else if (IS_ENABLED(CONFIG_DISK_DRIVER_SDMMC)) {
    		mnt->mnt_point = "/SD:";
    	} else {
    		mnt->mnt_point = "/NAND:";
    	}
    #endif
    	rc = fs_mount(mnt);
    
    	return rc;
    }
    
    static void setup_disk(void)
    {
    	struct fs_mount_t *mp = &fs_mnt;
    	struct fs_dir_t dir;
    	struct fs_statvfs sbuf;
    	int rc;
    
    	fs_dir_t_init(&dir);
    
    	if (IS_ENABLED(CONFIG_DISK_DRIVER_FLASH)) {
    		rc = setup_flash(mp);
    		if (rc < 0) {
    			LOG_ERR("Failed to setup flash area");
    			return;
    		}
    	}
    
    	if (!IS_ENABLED(CONFIG_FAT_FILESYSTEM_ELM)) {
    		LOG_INF("No file system selected");
    		return;
    	}
    
    	rc = mount_app_fs(mp);
    	if (rc < 0) {
    		LOG_ERR("Failed to mount filesystem");
    		return;
    	}
    
    	/* Allow log messages to flush to avoid interleaved output */
    	k_sleep(K_MSEC(50));
    
    	printk("Mount %s: %d\n", fs_mnt.mnt_point, rc);
    
    	rc = fs_statvfs(mp->mnt_point, &sbuf);
    	if (rc < 0) {
    		printk("FAIL: statvfs: %d\n", rc);
    		return;
    	}
    
    	printk("%s: bsize = %lu ; frsize = %lu ;"
    	       " blocks = %lu ; bfree = %lu\n",
    	       mp->mnt_point,
    	       sbuf.f_bsize, sbuf.f_frsize,
    	       sbuf.f_blocks, sbuf.f_bfree);
    
    	rc = fs_opendir(&dir, mp->mnt_point);
    	printk("%s opendir: %d\n", mp->mnt_point, rc);
    
    	if (rc < 0) {
    		LOG_ERR("Failed to open directory");
    	}
    
    	while (rc >= 0) {
    		struct fs_dirent ent = { 0 };
    
    		rc = fs_readdir(&dir, &ent);
    		if (rc < 0) {
    			LOG_ERR("Failed to read directory entries");
    			break;
    		}
    		if (ent.name[0] == 0) {
    			printk("End of files\n");
    			break;
    		}
    		printk("  %c %u %s\n",
    		       (ent.type == FS_DIR_ENTRY_FILE) ? 'F' : 'D',
    		       ent.size,
    		       ent.name);
    	}
    
    	(void)fs_closedir(&dir);
    
    	return;
    }
    
    struct mgmt_callback my_callback;
    enum mgmt_cb_return my_cb_func(uint32_t event, enum mgmt_cb_return prev_status,
                                		int32_t *rc, uint16_t *group, bool *abort_more,
                                    	void *data, size_t data_size)
    {
    	printk("MGMT CALLBACK\n");
    
    	*rc = MGMT_ERR_EACCESSDENIED;
    	return MGMT_CB_ERROR_RC;
    }
    
    int main(void)
    {
    	int ret;
    
    	setup_disk();
    
    	ret = usb_enable(NULL);
    	if (ret != 0) {
    		LOG_ERR("Failed to enable USB");
    		return 0;
    	}
    	LOG_INF("The device is put in USB mass storage mode.\n");
    
    	my_callback.callback = my_cb_func;
    	my_callback.event_id = MGMT_EVT_OP_FS_MGMT_FILE_ACCESS;
    	mgmt_callback_register(&my_callback);
    	printk("\nFS CALLBACK REGISTERED\n");
    
    	struct fs_file_t file;
    	fs_file_t_init(&file);
    	struct fs_dirent ent;					// dir entry struct for filling w file stats
    	fs_stat(READ_PATH, &ent);				// fill ent w stats for file @ READ_PATH
    	printk("%s\t(%d)\n",ent.name,ent.size);	// print stats for ent
    	char rstr[ent.size+1];						// initialize destination string w length >= ent.size
    	rstr[ent.size] = '\0';
    
    	size_t readSize = 100;			// num of bytes in most recent read
    	char rbuf[readSize+1];							// buffer string for reading chunks direcctly from file
    
    	LOG_INF("Reading %s\n",READ_PATH);
    	fs_open(&file, READ_PATH, FS_O_READ);			// open file for reading
    	for(int i = 0; i < ent.size; i += readSize)
    	{
    		readSize = fs_read(&file, rbuf, readSize);		// attempt to read *readSize* bytes into rbuf and save number of bytes actually read
    		rbuf[readSize] = '\0';
    		memcpy(rstr+i, rbuf, readSize);					// copy the read bytes from rbuf to the corresponding index of rstr
    		printk("%s",rbuf);
    		k_msleep(10);
    	}
    	fs_close(&file);								// close file to make conditions of memcpy as similar as possible
    	printk("\n");
    	return 0;
    }

    CONFIG_STDOUT_CONSOLE=y
    CONFIG_MAIN_STACK_SIZE=20000
    
    #USB related configs
    CONFIG_USB_DEVICE_STACK=y
    CONFIG_USB_DEVICE_PRODUCT="Zephyr MSC sample"
    CONFIG_USB_DEVICE_PID=0x0008
    CONFIG_LOG=y
    CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y
    CONFIG_USB_MASS_STORAGE=y
    CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y
    CONFIG_USB_MASS_STORAGE_LOG_LEVEL_ERR=y
    CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
    
    CONFIG_APP_MSC_STORAGE_FLASH_FATFS=y
    CONFIG_FILE_SYSTEM=y
    CONFIG_FILE_SYSTEM_MKFS=y
    CONFIG_APP_WIPE_STORAGE=y
    
    CONFIG_FS_FATFS_EXFAT=y
    CONFIG_USB_WORKQUEUE=n
    CONFIG_RESET_ON_FATAL_ERROR=n
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1024
    
    # cJSON Lib
    CONFIG_NEWLIB_LIBC=y
    CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
    CONFIG_CJSON_LIB=y
    
    # FS Shell
    CONFIG_SHELL=y
    CONFIG_HEAP_MEM_POOL_SIZE=2048
    CONFIG_FILE_SYSTEM_SHELL=y
    
    # FS Access Callback
    CONFIG_ZCBOR=y
    CONFIG_NET_BUF=y
    CONFIG_MCUMGR=y
    CONFIG_MCUMGR_GRP_FS=y
    CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS=y
    CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK=y

    Thanks,
    Micl

  • I have now confirmed with the developer, that there is no such feature implemented yet. Perhaps there will be some feature implemented in the future.

  • Hello Hakon,

    I'm also looking for a workaround on this topic.

    Would it be possible to have a thread poll the last-modified time of a file (to issue a reset with that information)? Or is modification time also under the "no such feature implemented yet" umbrella?

    Hopeful for advice,

        - Finn

  • You will need to implement this timestamp functionality yourself as far as I understand. I don't think the filesystem keeps track of last-modified time, but I could be wrong.

Related