From e715b77307fe3a36cfa9a487ff2a5fa0b5cf421c Mon Sep 17 00:00:00 2001 From: Takeshi Nakatani Date: Sun, 12 Feb 2023 17:59:40 +0900 Subject: [PATCH] Added the function to update mtime/ctime of the parent directory (#2016) --- doc/man/s3fs.1.in | 4 + src/fdcache_entity.h | 1 + src/s3fs.cpp | 192 ++++++++++++++++++++++- src/s3fs_help.cpp | 6 + test/integration-test-main.sh | 273 ++++++++++++++++++++++++++++++++- test/small-integration-test.sh | 2 +- 6 files changed, 469 insertions(+), 9 deletions(-) diff --git a/doc/man/s3fs.1.in b/doc/man/s3fs.1.in index 72690e5..041e4a5 100644 --- a/doc/man/s3fs.1.in +++ b/doc/man/s3fs.1.in @@ -422,6 +422,10 @@ If the cache is enabled, you can check the integrity of the cache file and the c This option is specified and when sending the SIGUSR1 signal to the s3fs process checks the cache status at that time. This option can take a file path as parameter to output the check result to that file. The file path parameter can be omitted. If omitted, the result will be output to stdout or syslog. +.TP +\fB\-o\fR update_parent_dir_stat (default is disable) +The parent directory's mtime and ctime are updated when a file or directory is created or deleted (when the parent directory's inode is updated). +By default, parent directory statistics are not updated. .SS "utility mode options" .TP \fB\-u\fR or \fB\-\-incomplete\-mpu\-list\fR diff --git a/src/fdcache_entity.h b/src/fdcache_entity.h index 7a88134..1909a56 100644 --- a/src/fdcache_entity.h +++ b/src/fdcache_entity.h @@ -149,6 +149,7 @@ class FdEntity bool PunchHole(off_t start = 0, size_t size = 0); void MarkDirtyNewFile(); + bool IsDirtyNewFile() { return (CREATE_FILE_PENDING == pending_status); } bool GetLastUpdateUntreatedPart(off_t& start, off_t& size) const; bool ReplaceLastUpdateUntreatedPart(off_t front_start, off_t front_size, off_t behind_start, off_t behind_size); diff --git a/src/s3fs.cpp b/src/s3fs.cpp index f5e4f43..9a54a3a 100644 --- a/src/s3fs.cpp +++ b/src/s3fs.cpp @@ -99,6 +99,7 @@ static off_t max_dirty_data = 5LL * 1024LL * 1024LL * 1024LL; static bool use_wtf8 = false; static off_t fake_diskfree_size = -1; // default is not set(-1) static int max_thread_count = 5; // default is 5 +static bool update_parent_dir_stat= false; // default not updating parent directory stats //------------------------------------------------------------------- // Global functions : prototype @@ -128,6 +129,7 @@ static int rename_object(const char* from, const char* to, bool update_ctime); static int rename_object_nocopy(const char* from, const char* to, bool update_ctime); static int clone_directory_object(const char* from, const char* to, bool update_ctime, const char* pxattrvalue); static int rename_directory(const char* from, const char* to); +static int update_mctime_parent_directory(const char* _path); static int remote_mountpath_exists(const char* path); static bool get_meta_xattr_value(const char* path, std::string& rawvalue); static bool get_parent_meta_xattr_value(const char* path, std::string& rawvalue); @@ -994,6 +996,13 @@ static int s3fs_mknod(const char *_path, mode_t mode, dev_t rdev) return result; } StatCache::getStatCacheData()->DelStat(path); + + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to mknod the file(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + S3FS_MALLOCTRIM(0); return result; @@ -1133,6 +1142,13 @@ static int s3fs_mkdir(const char* _path, mode_t mode) result = create_directory_object(path, mode, now, now, now, pcxt->uid, pcxt->gid, pxattrvalue); StatCache::getStatCacheData()->DelStat(path); + + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to create the directory(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + S3FS_MALLOCTRIM(0); return result; @@ -1153,6 +1169,13 @@ static int s3fs_unlink(const char* _path) StatCache::getStatCacheData()->DelStat(path); StatCache::getStatCacheData()->DelSymlink(path); FdManager::DeleteCacheFile(path); + + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to remove the file(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + S3FS_MALLOCTRIM(0); return result; @@ -1225,6 +1248,13 @@ static int s3fs_rmdir(const char* _path) strpath += "_$folder$"; result = s3fscurl.DeleteRequest(strpath.c_str()); } + + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to remove the directory(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + S3FS_MALLOCTRIM(0); return result; @@ -1297,6 +1327,13 @@ static int s3fs_symlink(const char* _from, const char* _to) if(!StatCache::getStatCacheData()->AddSymlink(std::string(to), strFrom)){ S3FS_PRN_ERR("failed to add symbolic link cache for %s", to); } + + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(to))){ + S3FS_PRN_ERR("succeed to create symbolic link(%s), but could not update timestamp of its parent directory(result=%d).", to, update_result); + } + S3FS_MALLOCTRIM(0); return result; @@ -1321,6 +1358,7 @@ static int rename_object(const char* from, const char* to, bool update_ctime) if(0 != (result = get_object_attribute(from, &buf, &meta))){ return result; } + std::string strSourcePath = (mount_prefix.empty() && 0 == strcmp("/", from)) ? "//" : from; if(update_ctime){ @@ -1716,6 +1754,17 @@ static int s3fs_rename(const char* _from, const char* _to) result = rename_object_nocopy(from, to, true); // update ctime } } + + // update parent directory timestamp + // + // [NOTE] + // already updated timestamp for original path in above functions. + // + int update_result; + if(0 != (update_result = update_mctime_parent_directory(to))){ + S3FS_PRN_ERR("succeed to create the file/directory(%s), but could not update timestamp of its parent directory(result=%d).", to, update_result); + } + S3FS_MALLOCTRIM(0); return result; @@ -2151,6 +2200,107 @@ static timespec handle_utimens_special_values(timespec ts, timespec now, timespe } } +static int update_mctime_parent_directory(const char* _path) +{ + if(!update_parent_dir_stat){ + // Disable updating parent directory stat. + S3FS_PRN_DBG("Updating parent directory stats is disabled"); + return 0; + } + + WTF8_ENCODE(path) + int result; + std::string parentpath; // parent directory path + std::string nowpath; // now directory object path("dir" or "dir/" or "xxx_$folder$", etc) + std::string newpath; // directory path for the current version("dir/") + std::string nowcache; + headers_t meta; + struct stat stbuf; + struct timespec mctime; + struct timespec atime; + dirtype nDirType = DIRTYPE_UNKNOWN; + + S3FS_PRN_INFO2("[path=%s]", path); + + // get parent directory path + parentpath = mydirname(path); + + // check & get directory type + if(0 != (result = chk_dir_object_type(parentpath.c_str(), newpath, nowpath, nowcache, &meta, &nDirType))){ + return result; + } + + // get directory stat + // + // [NOTE] + // It is assumed that this function is called after the operation on + // the file is completed, so there is no need to check the permissions + // on the parent directory. + // + if(0 != (result = get_object_attribute(parentpath.c_str(), &stbuf))){ + // If there is not the target file(object), result is -ENOENT. + return result; + } + if(!S_ISDIR(stbuf.st_mode)){ + S3FS_PRN_ERR("path(%s) is not parent directory.", parentpath.c_str()); + return -EIO; + } + + // make atime/mtime/ctime for updating + s3fs_realtime(mctime); + set_stat_to_timespec(stbuf, ST_TYPE_ATIME, atime); + + if(0 == atime.tv_sec && 0 == atime.tv_nsec){ + atime = mctime; + } + + if(nocopyapi || IS_REPLACEDIR(nDirType) || IS_CREATE_MP_STAT(parentpath.c_str())){ + // Should rebuild directory object(except new type) + // Need to remove old dir("dir" etc) and make new dir("dir/") + std::string xattrvalue; + const char* pxattrvalue; + if(get_meta_xattr_value(path, xattrvalue)){ + pxattrvalue = xattrvalue.c_str(); + }else{ + pxattrvalue = NULL; + } + + // At first, remove directory old object + if(!nowpath.empty()){ + if(0 != (result = remove_old_type_dir(nowpath, nDirType))){ + return result; + } + } + if(!nowcache.empty()){ + StatCache::getStatCacheData()->DelStat(nowcache); + } + + // Make new directory object("dir/") + if(0 != (result = create_directory_object(newpath.c_str(), stbuf.st_mode, atime, mctime, mctime, stbuf.st_uid, stbuf.st_gid, pxattrvalue))){ + return result; + } + }else{ + std::string strSourcePath = (mount_prefix.empty() && "/" == nowpath) ? "//" : nowpath; + headers_t updatemeta; + updatemeta["x-amz-meta-mtime"] = str(mctime); + updatemeta["x-amz-meta-ctime"] = str(mctime); + updatemeta["x-amz-meta-atime"] = str(atime); + updatemeta["x-amz-copy-source"] = urlEncode(service_path + S3fsCred::GetBucket() + get_realpath(strSourcePath.c_str())); + updatemeta["x-amz-metadata-directive"] = "REPLACE"; + + merge_headers(meta, updatemeta, true); + + // upload meta for parent directory. + if(0 != (result = put_headers(nowpath.c_str(), meta, true))){ + return result; + } + StatCache::getStatCacheData()->DelStat(nowcache); + } + S3FS_MALLOCTRIM(0); + + return 0; +} + static int s3fs_utimens(const char* _path, const struct timespec ts[2]) { WTF8_ENCODE(path) @@ -2681,10 +2831,20 @@ static int s3fs_flush(const char* _path, struct fuse_file_info* fi) AutoFdEntity autoent; FdEntity* ent; if(NULL != (ent = autoent.GetExistFdEntity(path, static_cast(fi->fh)))){ + bool is_new_file = ent->IsDirtyNewFile(); + ent->UpdateMtime(true); // clear the flag not to update mtime. ent->UpdateCtime(); result = ent->Flush(static_cast(fi->fh), AutoLock::NONE, false); StatCache::getStatCacheData()->DelStat(path); + + if(is_new_file){ + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to create the file(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + } } S3FS_MALLOCTRIM(0); @@ -2704,11 +2864,21 @@ static int s3fs_fsync(const char* _path, int datasync, struct fuse_file_info* fi AutoFdEntity autoent; FdEntity* ent; if(NULL != (ent = autoent.GetExistFdEntity(path, static_cast(fi->fh)))){ + bool is_new_file = ent->IsDirtyNewFile(); + if(0 == datasync){ ent->UpdateMtime(); ent->UpdateCtime(); } result = ent->Flush(static_cast(fi->fh), AutoLock::NONE, false); + + if(is_new_file){ + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to create the file(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + } } S3FS_MALLOCTRIM(0); @@ -2751,12 +2921,22 @@ static int s3fs_release(const char* _path, struct fuse_file_info* fi) return -EIO; } + bool is_new_file = ent->IsDirtyNewFile(); + // TODO: correct locks held? int result = ent->UploadPending(static_cast(fi->fh), AutoLock::NONE); if(0 != result){ S3FS_PRN_ERR("could not upload pending data(meta, etc) for pseudo_fd(%llu) / path(%s)", (unsigned long long)(fi->fh), path); return result; } + + if(is_new_file){ + // update parent directory timestamp + int update_result; + if(0 != (update_result = update_mctime_parent_directory(path))){ + S3FS_PRN_ERR("succeed to create the file(%s), but could not update timestamp of its parent directory(result=%d).", path, update_result); + } + } } // check - for debug @@ -4214,23 +4394,21 @@ static bool set_mountpoint_attribute(struct stat& mpst) // static int set_bucket(const char* arg) { - char* bucket_name = strdup(arg); + // TODO: Mutates input. Consider some other tokenization. + char *bucket_name = const_cast(arg); if(strstr(arg, ":")){ if(strstr(arg, "://")){ S3FS_PRN_EXIT("bucket name and path(\"%s\") is wrong, it must be \"bucket[:/path]\".", arg); - free(bucket_name); return -1; } if(!S3fsCred::SetBucket(strtok(bucket_name, ":"))){ S3FS_PRN_EXIT("bucket name and path(\"%s\") is wrong, it must be \"bucket[:/path]\".", arg); - free(bucket_name); return -1; } char* pmount_prefix = strtok(NULL, ""); if(pmount_prefix){ if(0 == strlen(pmount_prefix) || '/' != pmount_prefix[0]){ S3FS_PRN_EXIT("path(%s) must be prefix \"/\".", pmount_prefix); - free(bucket_name); return -1; } mount_prefix = pmount_prefix; @@ -4240,11 +4418,9 @@ static int set_bucket(const char* arg) }else{ if(!S3fsCred::SetBucket(arg)){ S3FS_PRN_EXIT("bucket name and path(\"%s\") is wrong, it must be \"bucket[:/path]\".", arg); - free(bucket_name); return -1; } } - free(bucket_name); return 0; } @@ -4763,6 +4939,10 @@ static int my_fuse_opt_proc(void* data, const char* arg, int key, struct fuse_ar S3fsCurl::SetUnsignedPayload(true); return 0; } + if(0 == strcmp(arg, "update_parent_dir_stat")){ + update_parent_dir_stat = true; + return 0; + } if(is_prefix(arg, "host=")){ s3host = strchr(arg, '=') + sizeof(char); return 0; diff --git a/src/s3fs_help.cpp b/src/s3fs_help.cpp index 843a772..5dcc542 100644 --- a/src/s3fs_help.cpp +++ b/src/s3fs_help.cpp @@ -542,6 +542,12 @@ static const char help_string[] = " check result to that file. The file path parameter can be omitted.\n" " If omitted, the result will be output to stdout or syslog.\n" "\n" + " update_parent_dir_stat (default is disable)\n" + " The parent directory's mtime and ctime are updated when a file or\n" + " directory is created or deleted (when the parent directory's inode is\n" + " updated).\n" + " By default, parent directory statistics are not updated.\n" + "\n" "FUSE/mount Options:\n" "\n" " Most of the generic mount options described in 'man mount' are\n" diff --git a/test/integration-test-main.sh b/test/integration-test-main.sh index 17d8b98..6a5d7bf 100755 --- a/test/integration-test-main.sh +++ b/test/integration-test-main.sh @@ -733,7 +733,7 @@ function test_special_characters { # shellcheck disable=SC2010 ls 'special~' 2>&1 | grep -q 'No such file or directory' # shellcheck disable=SC2010 - ls 'specialĀµ' 2>&1 | grep -q 'No such file or directory' + ls 'specialĪ¼' 2>&1 | grep -q 'No such file or directory' ) mkdir "TOYOTA TRUCK 8.2.2" @@ -1292,6 +1292,272 @@ function test_update_chmod_opened_file() { rm_test_file "${ALT_TEST_TEXT_FILE}" } +function test_update_parent_directory_time_sub() { + if [ $# -ne 1 ]; then + echo "Internal error: parameter is wrong." + return 1 + fi + + # [NOTE] + # Skip test for mknod/mkfifo command. + # If run them, ctime/mtime of the parent directory will be updated. + # + local TEST_PARENTDIR_PARENT="${1}" + local TEST_PARENTDIR_FILE="${TEST_PARENTDIR_PARENT}/testfile" + local TEST_PARENTDIR_SYMFILE_BASE="testfile2" + local TEST_PARENTDIR_FILE_MV="${TEST_PARENTDIR_PARENT}/${TEST_PARENTDIR_SYMFILE_BASE}" + local TEST_PARENTDIR_SYMFILE="${TEST_PARENTDIR_PARENT}/symfile" + local TEST_PARENTDIR_SYMFILE_MV="${TEST_PARENTDIR_PARENT}/symfile2" + local TEST_PARENTDIR_DIR="${TEST_PARENTDIR_PARENT}/testdir" + local TEST_PARENTDIR_DIR_MV="${TEST_PARENTDIR_PARENT}/testdir2" + + # + # Create file -> Update parent directory's mtime/ctime + # + local base_atime; base_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local base_ctime; base_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local base_mtime; base_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + touch "${TEST_PARENTDIR_FILE}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "creating file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Update file -> Not update parent directory's atime/mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + touch "${TEST_PARENTDIR_FILE}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" != "${after_ctime}" ] || [ "${base_mtime}" != "${after_mtime}" ]; then + echo "updating file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} == ${after_ctime} ), mtime( ${base_mtime} == ${after_mtime} )" + return 1 + fi + + # + # Rename file -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + mv "${TEST_PARENTDIR_FILE}" "${TEST_PARENTDIR_FILE_MV}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "renaming file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Create symbolic link -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + ln -s "${TEST_PARENTDIR_SYMFILE_BASE}" "${TEST_PARENTDIR_SYMFILE}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "creating symbolic file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Update symbolic file -> Not update parent directory's atime/mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + touch "${TEST_PARENTDIR_SYMFILE}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" != "${after_ctime}" ] || [ "${base_mtime}" != "${after_mtime}" ]; then + echo "updating symbolic file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} == ${after_ctime} ), mtime( ${base_mtime} == ${after_mtime} )" + return 1 + fi + + # + # Rename symbolic link -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + mv "${TEST_PARENTDIR_SYMFILE}" "${TEST_PARENTDIR_SYMFILE_MV}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "renaming symbolic file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Delete symbolic link -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + rm "${TEST_PARENTDIR_SYMFILE_MV}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "deleting symbolic file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Delete file -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + rm "${TEST_PARENTDIR_FILE_MV}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "deleting file expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Create directory -> Update parent directory's mtime/ctime + # + local base_atime; base_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local base_ctime; base_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local base_mtime; base_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + mkdir "${TEST_PARENTDIR_DIR}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "creating directory expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Update directory -> Not update parent directory's atime/mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + touch "${TEST_PARENTDIR_DIR}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" != "${after_ctime}" ] || [ "${base_mtime}" != "${after_mtime}" ]; then + echo "updating directory expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} == ${after_ctime} ), mtime( ${base_mtime} == ${after_mtime} )" + return 1 + fi + + # + # Rename directory -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + mv "${TEST_PARENTDIR_DIR}" "${TEST_PARENTDIR_DIR_MV}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "renaming directory expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + # + # Delete directory -> Update parent directory's mtime/ctime + # + base_atime="${after_atime}" + base_ctime="${after_ctime}" + base_mtime="${after_mtime}" + + rm -r "${TEST_PARENTDIR_DIR_MV}" + + local after_atime; after_atime=$(get_atime "${TEST_PARENTDIR_PARENT}") + local after_ctime; after_ctime=$(get_ctime "${TEST_PARENTDIR_PARENT}") + local after_mtime; after_mtime=$(get_mtime "${TEST_PARENTDIR_PARENT}") + + if [ "${base_atime}" != "${after_atime}" ] || [ "${base_ctime}" = "${after_ctime}" ] || [ "${base_mtime}" = "${after_mtime}" ]; then + echo "deleting directory expected updating ctime/mtime: atime( ${base_atime} == ${after_atime} ), ctime( ${base_ctime} != ${after_ctime} ), mtime( ${base_mtime} != ${after_mtime} )" + return 1 + fi + + return 0 +} + +function test_update_parent_directory_time() { + describe "Testing update time of parent directory..." + + # + # Test sub directory + # + mk_test_dir + if ! test_update_parent_directory_time_sub "${TEST_DIR}"; then + echo "failed test about updating time of parent directory: ${TEST_DIR}" + return 1 + fi + rm -rf "${TEST_DIR}" + + # + # Test bucket top directory + # + # [NOTE] + # The current directory for test execution is "/testrun-xxxx". + # This test checks in the directory at the top of the bucket. + # + if ! test_update_parent_directory_time_sub ".."; then + echo "failed test about updating time of parent directory: ${TEST_DIR}" + return 1 + fi + + return 0 +} + function test_rm_rf_dir { describe "Test that rm -rf will remove directory with contents ..." # Create a dir with some files and directories @@ -2389,7 +2655,10 @@ function add_all_tests { fi add_tests test_update_directory_time_subdir add_tests test_update_chmod_opened_file - + # shellcheck disable=SC2009 + if ps u -p "${S3FS_PID}" | grep -q update_parent_dir_stat; then + add_tests test_update_parent_directory_time + fi # shellcheck disable=SC2009 if ! ps u -p "${S3FS_PID}" | grep -q use_xattr; then add_tests test_posix_acl diff --git a/test/small-integration-test.sh b/test/small-integration-test.sh index 98ffbdd..174d01c 100755 --- a/test/small-integration-test.sh +++ b/test/small-integration-test.sh @@ -42,7 +42,7 @@ export CACHE_DIR export ENSURE_DISKFREE_SIZE if [ -n "${ALL_TESTS}" ]; then FLAGS=( - "use_cache=${CACHE_DIR} -o ensure_diskfree=${ENSURE_DISKFREE_SIZE} -o fake_diskfree=${FAKE_FREE_DISK_SIZE} -o use_xattr" + "use_cache=${CACHE_DIR} -o ensure_diskfree=${ENSURE_DISKFREE_SIZE} -o fake_diskfree=${FAKE_FREE_DISK_SIZE} -o use_xattr -o update_parent_dir_stat" enable_content_md5 disable_noobj_cache "max_stat_cache_size=100"