Re: [PATCH] tracefs/eventfs: Use root and instance inodes as default ownership

From
Steven Rostedt <rostedt@goodmis.org>
Date
2024-01-04 19:09:13
Also in
linux-fsdevel, linux-trace-kernel, lkml
Reply to
Al Viro, 2024-01-04
On Thu, 4 Jan 2024 18:25:02 +0000
Al Viro [off-list ref] wrote:
On Thu, Jan 04, 2024 at 10:05:44AM -0500, Steven Rostedt wrote:
quoted
This is the "tribal knowledge" I'm talking about. I really didn't know how
the root dentry parent worked. I guess that makes sense, as it matches the
'..' of a directory, and the '/' directory '..' points to itself. Although
mounted file systems do not behave that way. My /proc/.. is '/'. I just
figured that the dentry->d_parent would be similar. Learn something everyday.  
What would you expect to happen if you have the same filesystem mounted in
several places?  Having separate dentry trees would be a nightmare - you'd
get cache coherency problems from hell.  It's survivable for procfs, but
for something like a normal local filesystem it'd become very painful.
And if we want them to share dentry tree, how do you choose where the ..
would lead from the root dentry?
My mistake was thinking that the dentry was attached more to the path than
the inode. But that doesn't seem to be the case. I wasn't sure if there was
a way to get to a dentry from the inode. I see the i_dentry list, which is
a list, where I got some of my idea that dentry was closer to path than inode.
The way it's done is that linkage between the trees is done separately -
there's a tree of struct mount (well, forest, really - different processes
can easily have separate trees, which is how namespaces are done) and
each node in the mount tree refers to a dentry (sub)tree in some filesystem
instance.  Location is represented by (mount, dentry) pair and handling of
.. is basically (modulo refcounting, locking, error handling, etc.)
	while dentry == subtree_root(mount) && mount != mountpoint_mount(mount)
		// cross into the mountpoint under it
		dentry = mountpoint_dentry(mount)
		mount = mountpoint_mount(mount)
	go_into(mount, dentry->d_parent)

Note that you can have e.g. /usr/lib/gcc/x86_64-linux-gnu/12 mounted on /mnt/blah:
; mount --bind /usr/lib/gcc/x86_64-linux-gnu/12 /mnt/blah
will do it.  Then e.g. /mnt/blah/include will resolve to the same dentry as
/usr/lib/gcc/x86_64-linux-gnu/12/include, etc.
; chdir /mnt/blah
; ls
32                 crtprec80.o        libgomp.so         libsanitizer.spec
cc1                g++-mapper-server  libgomp.spec       libssp_nonshared.a
cc1plus            include            libitm.a           libstdc++.a
collect2           libasan.a          libitm.so          libstdc++fs.a
crtbegin.o         libasan_preinit.o  libitm.spec        libstdc++.so
crtbeginS.o        libasan.so         liblsan.a          libsupc++.a
crtbeginT.o        libatomic.a        liblsan_preinit.o  libtsan.a
crtend.o           libatomic.so       liblsan.so         libtsan_preinit.o
crtendS.o          libbacktrace.a     liblto_plugin.so   libtsan.so
crtfastmath.o      libcc1.so          libobjc.a          libubsan.a
crtoffloadbegin.o  libgcc.a           libobjc_gc.a       libubsan.so
crtoffloadend.o    libgcc_eh.a        libobjc_gc.so      lto1
crtoffloadtable.o  libgcc_s.so        libobjc.so         lto-wrapper
crtprec32.o        libgcov.a          libquadmath.a      plugin
crtprec64.o        libgomp.a          libquadmath.so     x32

We obviously want .. to resolve to /mnt, though.
; ls ..
; ls /usr/lib/gcc/x86_64-linux-gnu/
12

So the trigger for "cross into underlying mountpoint" has to be "dentry is
the root of subtree mount refers to" - it depends upon the mount we are
in.
quoted
quoted
 Filesystem object contents belongs here; multiple hardlinks
have different dentries and the same inode.  
So, can I assume that an inode could only have as many dentries as hard
links? I know directories are only allowed to have a single hard link. Is
that why they can only have a single dentry?  
Not quite.  Single alias for directories is more about cache coherency
fun; we really can't afford multiple aliases for those.  For non-directories
it's possible to have an entirely disconnected dentry refering to that
sucker; if somebody hands you an fhandle with no indication of the parent
directory, you might end up having to do one of those, no matter how many
times you find the same inode later.  Not an issue for tracefs, though.
quoted
quoted
namespace: mount tree.  Unlike everything prior, this one is a part of
process state - same as descriptor table, mappings, etc.  
And I'm guessing namespace is for containers. At least that's what I've
been assuming they are for.  
It predates containers by quite a few years, but yes, that's one of the
users.  It is related to virtual machines, in the same sense the set
of memory mappings is - each thread can be thought of as a VM, with
a bunch of components.  Just as mmap() manipulates the virtual address
translation for the threads that share memory space with the caller,
mount() manipulates the pathname resolution for the threads that share
the namespace with the caller.
quoted
quoted
descriptor table: mapping from numbers to IO channels (opened files).  
This is that "process fd table" I mentioned above (I wrote that before
reading this).
  
quoted
Again, a part of process state.  dup() creates a new entry, with
reference to the same file as the old one; multiple open() of the  
Hmm, wouldn't "dup()" create another "file" that just points to the same
dentry? It wouldn't be the "same file", or did you mean "file" from the
user space point of view?  
No.  The difference between open() and dup() is that the latter will
result in a descriptor that really refers to the same file.  Current
IO position belongs to IO channel; it doesn't matter for e.g. terminals,
but for regular file it immediately becomes an issue.
	fd1 = open("foo", 0);
	fd2 = open("foo", 0);
	read(fd1, &c1, 1);
	read(fd2, &c2, 1);
will result in the first byte of foo read into c1 and c2, but
	fd1 = open("foo", 0);
	fd2 = dup(fd1);
	read(fd1, &c1, 1);
	read(fd2, &c2, 1);
will have the first byte of foo in c1 and the second one - in c2.
open() yields a new IO channel attached to new descriptor; dup()
(and dup2()) attaches the existing IO channel to new descriptor.
fork() acts like dup() in that respect - child gets its descriptor
table populated with references to the same IO channels as the
parent does.
Ah, looking at the code I use dup() in, it's mostly for pipes in
and for redirecting stdout,stdin, etc. So yeah, that makes sense.
Any Unix since about '71 has it done that way and the same goes
for NT, DOS, etc. - you can't implement redirects to/from regular
files without that distinction.
Yep, which is what I used it for. Just forgot the details.
quoted
quoted
same pathname will each yield a separate opened file.  _Some_ state
belongs here (close-on-exec, mostly).  Note that there's no such
thing as "the descriptor of this file" - not even "the user-supplied
number that had been used to get the file we are currently reading
from", since that number might be refering to something entirely
different right after we'd resolved it to opened file and that
happens *without* disrupting the operation.  
This last paragraph confused me. What do you mean by ""referring to
something entirely different"?  
	Two threads share descriptor table; one of them is in
read(fd, ...), another does dup2(fd2, fd).  If read() gets past the
point where it gets struct file reference, it will keep accessing that
IO channel.  dup2() will replace the reference in descriptor table,
but that won't disrupt the read()...
Oh, OK. So basically if fd 4 is a reference to /tmp/foo and you open
/tmp/bar which gets fd2, and one thread is reading fd 4 (/tmp/foo), the
other thread doing dup2(fd2, fd) will make fd 4 a reference to /tmp/bar but
the read will finish reading /tmp/foo.

But if the first thread were to do another read(fd, ...) it would then read
/tmp/bar. In other words, it allows read() to stay atomic with respect to
what it is reading until it returns.
quoted
Thanks for this overview. It was very useful, and something I think we
should add to kernel doc. I did read Documentation/filesystems/vfs.rst but
honestly, I think your writeup here is a better overview.  
At the very least it would need serious reordering ;-/
Yeah, but this is all great information. Thanks for explaining it.

-- Steve
Thread (10 messages) 10 messages, 4 authors, last reply 2024-01-04
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help