Hi, Attached is a proposed patch for computing the hard links counts whenever it is required, instead of relying on the value stored on disk. This obviously causes some extra delay when listing a crowded directory such as \Windows\System32.
This feature is activated by setting the mount option "hard_links". Please test and report. Note : this does not apply to directories. Counting the number of subdirectories is much more time consuming. Jean-Pierre Alexander Shchadilov wrote on 7/26/20 10:17 PM:
Hello, It seems that a counter in an MFT entry that is often referred to as a "hard link counter" (offset 0x12 - 0x13) is increased by one when Windows adds an 8.3 alias for a file name. Description of short names in Windows docs: "When you create a long file name, Windows may also create a short 8.3 form of the name, called the 8.3 alias or short name, and store it on disk also." ntfs-3g should differentiate hard links and short names, even if they are stored on disk in a similar way, and calculate an accurate number of hard links. I can not provide an established definition of a hard link from some Linux-related standard, but the following command in Windows 10 will not show a 8.3 name in its output: fsutil hardlink list <filename> >From fsutil documentation: A hard link is a directory entry for a file. Every file can be considered to have at least one hard link. On NTFS volumes, each file can have multiple hard links, so a single file can appear in many directories (or even in the same directory with different names). Considering that this definition is provided by Microsoft, the company that developed NTFS, and 8.3 names do not make a file "appear in the same directory with different names" when viewed by File Explorer in Windows, I think it is safe to tell that a short alias should be excluded from a number of hard links. This issue was discovered in the discussion of QDirStat bug (issue #88 on Github). Best wishes, Alexander Shchadilov
--- include/ntfs-3g/dir.h.ref 2020-07-27 10:00:34.320440500 +0200 +++ include/ntfs-3g/dir.h 2020-07-27 10:13:32.203376400 +0200 @@ -117,6 +117,7 @@ int ntfs_set_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni, const char *value, size_t size, int flags); int ntfs_remove_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni); +int ntfs_dir_link_cnt(ntfs_inode *ni); #if CACHE_INODE_SIZE --- src/ntfs-3g_common.h.ref 2020-07-27 10:00:33.812887100 +0200 +++ src/ntfs-3g_common.h 2020-07-27 10:13:01.554393800 +0200 @@ -92,6 +92,7 @@ OPT_USERMAPPING, OPT_XATTRMAPPING, OPT_EFS_RAW, + OPT_HARD_LINKS, } ; /* Option flags */ @@ -153,6 +154,7 @@ BOOL no_detach; BOOL blkdev; BOOL mounted; + BOOL hard_links; #ifdef HAVE_SETXATTR /* extended attributes interface required */ BOOL efs_raw; #ifdef XATTR_MAPPINGS --- src/ntfs-3g_common.c.ref 2020-07-27 10:00:33.922037800 +0200 +++ src/ntfs-3g_common.c 2020-07-27 10:12:53.206776300 +0200 @@ -126,6 +126,7 @@ { "usermapping", OPT_USERMAPPING, FLGOPT_STRING }, { "xattrmapping", OPT_XATTRMAPPING, FLGOPT_STRING }, { "efs_raw", OPT_EFS_RAW, FLGOPT_BOGUS }, + { "hard_links", OPT_HARD_LINKS, FLGOPT_BOGUS }, { (const char*)NULL, 0, 0 } /* end marker */ } ; @@ -492,6 +493,9 @@ ctx->efs_raw = TRUE; break; #endif /* HAVE_SETXATTR */ + case OPT_HARD_LINKS : + ctx->hard_links = TRUE; + break; case OPT_FSNAME : /* Filesystem name. */ /* * We need this to be able to check whether filesystem --- src/ntfs-3g.c.ref 2020-07-27 10:00:34.447994200 +0200 +++ src/ntfs-3g.c 2020-07-27 10:12:23.066081700 +0200 @@ -790,6 +790,10 @@ } #endif stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + if (ctx->hard_links + && !(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) + && !(ni->flags & FILE_ATTR_REPARSE_POINT)) + stbuf->st_nlink = ntfs_dir_link_cnt(ni); if (((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) || (ni->flags & FILE_ATTR_REPARSE_POINT)) --- src/lowntfs-3g.c.ref 2020-07-27 10:00:34.460471300 +0200 +++ src/lowntfs-3g.c 2020-07-27 10:12:39.119761300 +0200 @@ -645,6 +645,10 @@ memset(stbuf, 0, sizeof(struct stat)); withusermapping = (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL); stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + if (ctx->hard_links + && !(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) + && !(ni->flags & FILE_ATTR_REPARSE_POINT)) + stbuf->st_nlink = ntfs_dir_link_cnt(ni); if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) || (ni->flags & FILE_ATTR_REPARSE_POINT)) { if (ni->flags & FILE_ATTR_REPARSE_POINT) { --- libntfs-3g/dir.c.ref 2020-07-27 10:00:34.342375000 +0200 +++ libntfs-3g/dir.c 2020-07-27 10:31:46.527409400 +0200 @@ -2792,3 +2792,42 @@ } return (res); } + +/* + * Compute the number of names, excluding the short ones + */ +int ntfs_dir_link_cnt(ntfs_inode *ni) +{ + ntfs_attr_search_ctx *actx = NULL; + FILE_NAME_ATTR *fn = NULL; + int err = 0; + int nlink = 0; + + if (!ni) { + ntfs_log_error("Invalid argument.\n"); + errno = EINVAL; + goto err_out; + } + if (ni->nr_extents == -1) + ni = ni->base_ni; + /* + * Search for FILE_NAME attributes, and count those which are not + * DOS-only ones. + */ + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) + goto err_out; + while (!(err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, + CASE_SENSITIVE, 0, NULL, 0, actx))) { + fn = (FILE_NAME_ATTR*)((u8*)actx->attr + + le16_to_cpu(actx->attr->value_offset)); + if (fn->file_name_type != FILE_NAME_DOS) + nlink++; + } + if (err && (errno != ENOENT)) + nlink = 0; + if (actx) + ntfs_attr_put_search_ctx(actx); +err_out : + return (nlink); +}
_______________________________________________ ntfs-3g-devel mailing list ntfs-3g-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/ntfs-3g-devel