diff --git a/include/ntfs-3g/dir.h b/include/ntfs-3g/dir.h index ced83f43..60169151 100644 --- a/include/ntfs-3g/dir.h +++ b/include/ntfs-3g/dir.h @@ -117,6 +117,7 @@ int ntfs_get_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni, 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 diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 0721e782..5e5baf62 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -2792,3 +2792,82 @@ int ntfs_remove_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni) } return (res); } + +/* + * Increment the count of subdirectories + * (excluding entries with a short name) + */ + +static int nlink_increment(void *nlink_ptr, + const ntfschar *name __attribute__((unused)), + const int len __attribute__((unused)), + const int type, + const s64 pos __attribute__((unused)), + const MFT_REF mref __attribute__((unused)), + const unsigned int dt_type) +{ + if ((dt_type == NTFS_DT_DIR) && (type != FILE_NAME_DOS)) + (*((int*)nlink_ptr))++; + return (0); +} + +/* + * Compute the number of hard links according to Posix + * For a directory count the subdirectories whose name is not + * a short one, but count "." and ".." + * Otherwise count the names, excluding the short ones. + * + * if there is an error, a null count is returned. + */ + +int ntfs_dir_link_cnt(ntfs_inode *ni) +{ + ntfs_attr_search_ctx *actx; + FILE_NAME_ATTR *fn; + s64 pos; + 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; + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + /* + * Directory : scan the directory and count + * subdirectories whose name is not DOS-only. + * The directory names are ignored, but "." and ".." + * are taken into account. + */ + pos = 0; + err = ntfs_readdir(ni, &pos, &nlink, nlink_increment); + if (err) + nlink = 0; + } else { + /* + * Non-directory : 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; + ntfs_attr_put_search_ctx(actx); + } + if (!nlink) + ntfs_log_perror("Failed to compute nlink of inode %lld", + (long long)ni->mft_no); +err_out : + return (nlink); +} diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index a58c1c1a..7d5c3e8f 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -708,6 +708,9 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, 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->posix_nlink + && !(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) { @@ -770,7 +773,8 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, } stbuf->st_size = ni->data_size; stbuf->st_blocks = ni->allocated_size >> 9; - stbuf->st_nlink = 1; /* Make find(1) work */ + if (!ctx->posix_nlink) + stbuf->st_nlink = 1; /* Make find(1) work */ } } else { /* Regular or Interix (INTX) file. */ diff --git a/src/ntfs-3g.8.in b/src/ntfs-3g.8.in index 2f2e8ca2..2d18fb4b 100644 --- a/src/ntfs-3g.8.in +++ b/src/ntfs-3g.8.in @@ -250,6 +250,13 @@ they do not appear in Windows directory displays either. When a file is renamed or linked with a new name, the hidden flag is adjusted to the latest name. .TP +.B posix_nlink +Compute the count of hard links of a file or directory according to +the Posix specifications. When this option is not set, a count of 1 +is set for directories, and the short name of files is accounted for. +Using the option entails some penalty as the count is not stored and +has to be computed. +.TP .B windows_names This option prevents files, directories and extended attributes to be created with a name not allowed by windows, because diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index fdb42948..2fd98285 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -790,6 +790,9 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) } #endif stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + if (ctx->posix_nlink + && !(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)) @@ -852,7 +855,8 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) } stbuf->st_size = ni->data_size; stbuf->st_blocks = ni->allocated_size >> 9; - stbuf->st_nlink = 1; /* Make find(1) work */ + if (!ctx->posix_nlink) + stbuf->st_nlink = 1; /* Make find(1) work */ } } else { /* Regular or Interix (INTX) file. */ diff --git a/src/ntfs-3g_common.c b/src/ntfs-3g_common.c index a8ea6385..397b72a0 100644 --- a/src/ntfs-3g_common.c +++ b/src/ntfs-3g_common.c @@ -1,7 +1,7 @@ /** * ntfs-3g_common.c - Common definitions for ntfs-3g and lowntfs-3g. * - * Copyright (c) 2010-2019 Jean-Pierre Andre + * Copyright (c) 2010-2020 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -126,6 +126,7 @@ const struct DEFOPTION optionlist[] = { { "usermapping", OPT_USERMAPPING, FLGOPT_STRING }, { "xattrmapping", OPT_XATTRMAPPING, FLGOPT_STRING }, { "efs_raw", OPT_EFS_RAW, FLGOPT_BOGUS }, + { "posix_nlink", OPT_POSIX_NLINK, FLGOPT_BOGUS }, { (const char*)NULL, 0, 0 } /* end marker */ } ; @@ -492,6 +493,9 @@ char *parse_mount_options(ntfs_fuse_context_t *ctx, ctx->efs_raw = TRUE; break; #endif /* HAVE_SETXATTR */ + case OPT_POSIX_NLINK : + ctx->posix_nlink = TRUE; + break; case OPT_FSNAME : /* Filesystem name. */ /* * We need this to be able to check whether filesystem diff --git a/src/ntfs-3g_common.h b/src/ntfs-3g_common.h index bcffe4f7..8c2ecf39 100644 --- a/src/ntfs-3g_common.h +++ b/src/ntfs-3g_common.h @@ -92,6 +92,7 @@ enum { OPT_USERMAPPING, OPT_XATTRMAPPING, OPT_EFS_RAW, + OPT_POSIX_NLINK, } ; /* Option flags */ @@ -153,6 +154,7 @@ typedef struct { BOOL no_detach; BOOL blkdev; BOOL mounted; + BOOL posix_nlink; #ifdef HAVE_SETXATTR /* extended attributes interface required */ BOOL efs_raw; #ifdef XATTR_MAPPINGS