2009-12-06 07:59:46

by J. R. Okajima

[permalink] [raw]
Subject: [RFC 0/5] pathconf(3) with _PC_LINK_MAX

The pathconf(_PC_LINK_MAX) cannot get the correct value, since linux
kernel doesn't provide such interface. And the current implementation in
GLibc issues statfs(2) first and then returns the predefined value
(EXT2_LINK_MAX, etc) based upoin the filesystem type. But GLibc doesn't
support all filesystem types. ie. when the target filesystem is unknown
to pathconf(3), it will return LINUX_LINK_MAX (127).
For GLibc, there is no way except implementing this poor method.

This patch makes statfs(2) return the correct value via struct
statfs.f_spare[0].

RFC:
- Can we use f_spare for this purpose?
- Does pathconf(_PC_LINK_MAX) distinguish a dir and a non-dir?
If a filesystem sets different limit for a dir as a link count from a
non-dir, then should the filesystem checks the type of the specified
dentry->d_inode->i_mode and return the different value?
This patch series doesn't distinguish them and return a single value.
- Here I tried supporting only ext[23], nfs and tmpfs. Since I can test
them by myself. I left other FSs as it is, which means if FS doesn't
support _PC_LINK_MAX by modifying its s_op->statfs(), the default
value will be returned. The default value is taken from GLibc trying
to keep the compatibility. But it may not be important.
- Some FS such as ms-dos based one which doesn't support hardlink, will
return LINK_MAX_UNSUPPORTED which is defined as 1.
- Other FS such as tmpfs which doesn't check the link count in link(2),
will return LINK_MAX_UNLIMITED which is defined as -1. This value
doesn't mean an error. The negative return value of pathconf(3) is
valid.

Even if linux kernel return a correct value via statfs(2) (or anything
else), users will not get the value at once since the support in libc is
necessary too.


J. R. Okajima (5):
vfs, support pathconf(3) with _PC_LINK_MAX
ext2, support pathconf(3) with _PC_LINK_MAX
ext3, support pathconf(3) with _PC_LINK_MAX
nfs, support pathconf(3) with _PC_LINK_MAX
tmpfs, support pathconf(3) with _PC_LINK_MAX

fs/compat.c | 5 +++--
fs/ext2/super.c | 1 +
fs/ext3/super.c | 1 +
fs/libfs.c | 1 +
fs/nfs/client.c | 10 +++++++---
fs/nfs/super.c | 1 +
fs/open.c | 9 +++++++--
include/linux/nfs_fs_sb.h | 1 +
include/linux/statfs.h | 6 ++++++
mm/shmem.c | 1 +
10 files changed, 29 insertions(+), 7 deletions(-)


2009-12-06 07:59:52

by J. R. Okajima

[permalink] [raw]
Subject: [RFC 1/5] vfs, support pathconf(3) with _PC_LINK_MAX

The pathconf(_PC_LINK_MAX) cannot get the correct value, since linux
kernel doesn't provide such interface. And the current implementation in
GLibc issues statfs(2) first and then returns the predefined value
(EXT2_LINK_MAX, etc) based upoin the filesystem type. But GLibc doesn't
support all filesystem types. ie. when the target filesystem is unknown
to pathconf(3), it will return LINUX_LINK_MAX (127).
For GLibc, there is no way except implementing this poor method.

This patch makes statfs(2) return the correct value via struct
statfs.f_spare[0].

Signed-off-by: J. R. Okajima <[email protected]>
---
fs/compat.c | 5 +++--
fs/libfs.c | 1 +
fs/open.c | 9 +++++++--
include/linux/statfs.h | 6 ++++++
4 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/fs/compat.c b/fs/compat.c
index 6c19040..fd1d96b 100644
--- a/fs/compat.c
+++ b/fs/compat.c
@@ -246,7 +246,7 @@ static int put_compat_statfs(struct compat_statfs __user *ubuf, struct kstatfs *
__put_user(kbuf->f_fsid.val[0], &ubuf->f_fsid.val[0]) ||
__put_user(kbuf->f_fsid.val[1], &ubuf->f_fsid.val[1]) ||
__put_user(kbuf->f_frsize, &ubuf->f_frsize) ||
- __put_user(0, &ubuf->f_spare[0]) ||
+ __put_user(kbuf->f_linkmax, &ubuf->f_linkmax) || /* f_spare[0] */
__put_user(0, &ubuf->f_spare[1]) ||
__put_user(0, &ubuf->f_spare[2]) ||
__put_user(0, &ubuf->f_spare[3]) ||
@@ -319,7 +319,8 @@ static int put_compat_statfs64(struct compat_statfs64 __user *ubuf, struct kstat
__put_user(kbuf->f_namelen, &ubuf->f_namelen) ||
__put_user(kbuf->f_fsid.val[0], &ubuf->f_fsid.val[0]) ||
__put_user(kbuf->f_fsid.val[1], &ubuf->f_fsid.val[1]) ||
- __put_user(kbuf->f_frsize, &ubuf->f_frsize))
+ __put_user(kbuf->f_frsize, &ubuf->f_frsize) ||
+ __put_user(kbuf->f_linkmax, &ubuf->f_linkmax)) /* f_spare[0] */
return -EFAULT;
return 0;
}
diff --git a/fs/libfs.c b/fs/libfs.c
index 219576c..1a078dd 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -28,6 +28,7 @@ int simple_statfs(struct dentry *dentry, struct kstatfs *buf)
buf->f_type = dentry->d_sb->s_magic;
buf->f_bsize = PAGE_CACHE_SIZE;
buf->f_namelen = NAME_MAX;
+ buf->f_linkmax = LINK_MAX_UNLIMITED; /* cf. simple_link() */
return 0;
}

diff --git a/fs/open.c b/fs/open.c
index 4f01e06..4ca513f 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -39,6 +39,7 @@ int vfs_statfs(struct dentry *dentry, struct kstatfs *buf)
retval = -ENOSYS;
if (dentry->d_sb->s_op->statfs) {
memset(buf, 0, sizeof(*buf));
+ buf->f_linkmax = LINK_MAX_DEFAULT;
retval = security_sb_statfs(dentry);
if (retval)
return retval;
@@ -91,7 +92,9 @@ static int vfs_statfs_native(struct dentry *dentry, struct statfs *buf)
buf->f_fsid = st.f_fsid;
buf->f_namelen = st.f_namelen;
buf->f_frsize = st.f_frsize;
- memset(buf->f_spare, 0, sizeof(buf->f_spare));
+ /* contain f_linkmax */
+ BUILD_BUG_ON(sizeof(buf->f_spare) < sizeof(st.f_spare));
+ memcpy(buf->f_spare, st.f_spare, sizeof(st.f_spare));
}
return 0;
}
@@ -118,7 +121,9 @@ static int vfs_statfs64(struct dentry *dentry, struct statfs64 *buf)
buf->f_fsid = st.f_fsid;
buf->f_namelen = st.f_namelen;
buf->f_frsize = st.f_frsize;
- memset(buf->f_spare, 0, sizeof(buf->f_spare));
+ /* contain f_linkmax */
+ BUILD_BUG_ON(sizeof(buf->f_spare) < sizeof(st.f_spare));
+ memcpy(buf->f_spare, st.f_spare, sizeof(st.f_spare));
}
return 0;
}
diff --git a/include/linux/statfs.h b/include/linux/statfs.h
index b34cc82..958b837 100644
--- a/include/linux/statfs.h
+++ b/include/linux/statfs.h
@@ -19,4 +19,10 @@ struct kstatfs {
long f_spare[5];
};

+/* support pathconf(3) with _PC_LINK_MAX */
+#define f_linkmax f_spare[0]
+#define LINK_MAX_UNLIMITED (-1) /* link(2) does not check i_nlink */
+#define LINK_MAX_UNSUPPORTED 1 /* hardlink is unavailable */
+#define LINK_MAX_DEFAULT 127 /* compatibility to glibc */
+
#endif
--
1.6.1.284.g5dc13

2009-12-06 08:00:02

by J. R. Okajima

[permalink] [raw]
Subject: [RFC 2/5] ext2, support pathconf(3) with _PC_LINK_MAX

Return the value via struct statfs.f_spare[0].

Signed-off-by: J. R. Okajima <[email protected]>
---
fs/ext2/super.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/fs/ext2/super.c b/fs/ext2/super.c
index 1a9ffee..720c5c1 100644
--- a/fs/ext2/super.c
+++ b/fs/ext2/super.c
@@ -1304,6 +1304,7 @@ static int ext2_statfs (struct dentry * dentry, struct kstatfs * buf)
le64_to_cpup((void *)es->s_uuid + sizeof(u64));
buf->f_fsid.val[0] = fsid & 0xFFFFFFFFUL;
buf->f_fsid.val[1] = (fsid >> 32) & 0xFFFFFFFFUL;
+ buf->f_linkmax = EXT2_LINK_MAX;
return 0;
}

--
1.6.1.284.g5dc13

2009-12-06 07:59:16

by J. R. Okajima

[permalink] [raw]
Subject: [RFC 3/5] ext3, support pathconf(3) with _PC_LINK_MAX

Return the value via struct statfs.f_spare[0].

Signed-off-by: J. R. Okajima <[email protected]>
---
fs/ext3/super.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/fs/ext3/super.c b/fs/ext3/super.c
index 427496c..19cdc14 100644
--- a/fs/ext3/super.c
+++ b/fs/ext3/super.c
@@ -2698,6 +2698,7 @@ static int ext3_statfs (struct dentry * dentry, struct kstatfs * buf)
le64_to_cpup((void *)es->s_uuid + sizeof(u64));
buf->f_fsid.val[0] = fsid & 0xFFFFFFFFUL;
buf->f_fsid.val[1] = (fsid >> 32) & 0xFFFFFFFFUL;
+ buf->f_linkmax = EXT3_LINK_MAX;
return 0;
}

--
1.6.1.284.g5dc13

2009-12-06 08:00:22

by J. R. Okajima

[permalink] [raw]
Subject: [RFC 4/5] nfs, support pathconf(3) with _PC_LINK_MAX

Return the value via struct statfs.f_spare[0], based upon PATHCONF
procedure. As namemax, it is set to a new member in struct nfs_server,
named linkmax.
Since NFSv2 (v4 too?) doesn't have PATHCONF procedure, it will be set to
LINK_MAX_DEFAULT which is a default value of GLibc implementation.

Signed-off-by: J. R. Okajima <[email protected]>
---
fs/nfs/client.c | 10 +++++++---
fs/nfs/super.c | 1 +
include/linux/nfs_fs_sb.h | 1 +
3 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index 99ea196..ed3fddd 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -851,6 +851,7 @@ static int nfs_init_server(struct nfs_server *server,
server->mountd_protocol = data->mount_server.protocol;

server->namelen = data->namlen;
+ server->linkmax = LINK_MAX_DEFAULT;
/* Create a client RPC handle for the NFSv3 ACL management interface */
nfs_init_server_aclclient(server);
dprintk("<-- nfs_init_server() = 0 [new %p]\n", clp);
@@ -941,14 +942,17 @@ static int nfs_probe_fsinfo(struct nfs_server *server, struct nfs_fh *mntfh, str
nfs_server_set_fsinfo(server, &fsinfo);

/* Get some general file system info */
- if (server->namelen == 0) {
+ {
struct nfs_pathconf pathinfo;

pathinfo.fattr = fattr;
nfs_fattr_init(fattr);

- if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0)
- server->namelen = pathinfo.max_namelen;
+ if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0) {
+ if (server->namelen == 0)
+ server->namelen = pathinfo.max_namelen;
+ server->linkmax = pathinfo.max_link;
+ }
}

dprintk("<-- nfs_probe_fsinfo() = 0\n");
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 90be551..ae0599c 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -453,6 +453,7 @@ static int nfs_statfs(struct dentry *dentry, struct kstatfs *buf)
buf->f_ffree = res.afiles;

buf->f_namelen = server->namelen;
+ buf->f_linkmax = server->linkmax;

return 0;

diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 320569e..d66ab43 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -123,6 +123,7 @@ struct nfs_server {
unsigned int acdirmin;
unsigned int acdirmax;
unsigned int namelen;
+ unsigned int linkmax;
unsigned int options; /* extra options enabled by mount */
#define NFS_OPTION_FSCACHE 0x00000001 /* - local caching enabled */

--
1.6.1.284.g5dc13

2009-12-06 07:59:39

by J. R. Okajima

[permalink] [raw]
Subject: [RFC 5/5] tmpfs, support pathconf(3) with _PC_LINK_MAX

Return the value via struct statfs.f_spare[0].
Actually tmpfs doesn't check the link count in link(2) and it can be
wrap around. Set LINK_MAX_UNLIMITED.

Signed-off-by: J. R. Okajima <[email protected]>
---
mm/shmem.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/mm/shmem.c b/mm/shmem.c
index 356dd99..78f7e21 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1784,6 +1784,7 @@ static int shmem_statfs(struct dentry *dentry, struct kstatfs *buf)
buf->f_type = TMPFS_MAGIC;
buf->f_bsize = PAGE_CACHE_SIZE;
buf->f_namelen = NAME_MAX;
+ buf->f_linkmax = LINK_MAX_UNLIMITED;
spin_lock(&sbinfo->stat_lock);
if (sbinfo->max_blocks) {
buf->f_blocks = sbinfo->max_blocks;
--
1.6.1.284.g5dc13

2009-12-06 08:40:02

by Al Viro

[permalink] [raw]
Subject: Re: [RFC 0/5] pathconf(3) with _PC_LINK_MAX

On Sun, Dec 06, 2009 at 04:58:58PM +0900, J. R. Okajima wrote:
> The pathconf(_PC_LINK_MAX) cannot get the correct value, since linux
> kernel doesn't provide such interface. And the current implementation in
> GLibc issues statfs(2) first and then returns the predefined value
> (EXT2_LINK_MAX, etc) based upoin the filesystem type. But GLibc doesn't
> support all filesystem types. ie. when the target filesystem is unknown
> to pathconf(3), it will return LINUX_LINK_MAX (127).
> For GLibc, there is no way except implementing this poor method.
>
> This patch makes statfs(2) return the correct value via struct
> statfs.f_spare[0].

Um... Why do we need that, again? Note that there is no way whatsoever
for predicting whether link(2) will fail due to having too many existing
links before you attempt the call - links can be created or removed between
stat(2) and link(2). So any uses of that value are heuristical.

Can you actually show any use cases of that thing? Preferably - in existing
code, but even a theoretical one would be interesting.

2009-12-06 09:09:14

by J. R. Okajima

[permalink] [raw]
Subject: Re: [RFC 0/5] pathconf(3) with _PC_LINK_MAX


Al Viro:
> Um... Why do we need that, again? Note that there is no way whatsoever
> for predicting whether link(2) will fail due to having too many existing
> links before you attempt the call - links can be created or removed between
> stat(2) and link(2). So any uses of that value are heuristical.
>
> Can you actually show any use cases of that thing? Preferably - in existing
> code, but even a theoretical one would be interesting.

Thanx for quick reply.
To be honest, I am unsure how important this is in real world.
But I've met (reported, precisly) such test program. It seems to come
from old X/Open, though the actual reporter tried the LSB (Linux
Standard Base) runtime-test.

You can get the source code from
ftp.freestandards.org/pub/lsb/test_suites/released-3.2/source/runtime/lsb-test-core-3.2.0-2.src.rpm
+ lsb-test-core-3.2.0.tar.gz
+ lts_vsx-pcts-3.2.0.tgz
+ tset/POSIX.os/files/link/link.c

Here I quote just a part from tset/POSIX.os/files/link/link.c,
The function tblink() behaves as a wrapper for link(2) with the error
checking.
When the filesystem is unknown to pathconf(_PC_LINK_MAX), 'link_max' may
be incorrect and LSB cannot pass this test.

test15()
{
creat(t15a_file, MODEANY);
link_max = pathconf(t15a_file, _PC_LINK_MAX);

/* create lesser of LINK_MAX and PCTS_LINK_MAX links successfully */
/* i.e. make (testmax-1) link() calls, as there is 1 link already */

testmax = link_max;
for (i = 0; i < testmax-1; i++)
{
(void) sprintf(links[i], "L%ld", i+1);
if (tblink(t15a_file, links[i], SUCCEED, NOERROR) != SUCCEED)
break;
}

/* if LINK_MAX is testable, next link gives EMLINK */

(void) tblink(t15a_file, t15b_file, SYSERROR, EMLINK);
}


J. R. Okajima

2009-12-06 21:39:33

by Theodore Ts'o

[permalink] [raw]
Subject: Re: [RFC 0/5] pathconf(3) with _PC_LINK_MAX

On Sun, Dec 06, 2009 at 08:39:58AM +0000, Al Viro wrote:
>
> Um... Why do we need that, again? Note that there is no way whatsoever
> for predicting whether link(2) will fail due to having too many existing
> links before you attempt the call - links can be created or removed between
> stat(2) and link(2). So any uses of that value are heuristical.
>
> Can you actually show any use cases of that thing? Preferably - in existing
> code, but even a theoretical one would be interesting.

I think it's mainly a "if we're going to implement a POSIX interface,
it would be nice if it returned something based on reality instead of
a wild-assed guess". :-)

The "real life" use case I could think of is that backup programs that
use hard links everywhere would be able to determine ahead of time in
advance when it might need to create a new file instead of using a
hard link, without needing to do the link and getting the EMLINK
error. I agree that the only way you can know for sure is by actually
trying the link, so it's a pretty feeble use case.

I will note that without a functional, ext3 and ext4 (or ext3
filesystem with dir_nlink file system feature mounted with ext4) file
systems would be indistinguishable.

- Ted