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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, 4 Jan 2024 18:25:02 +0000
Al Viro <viro@xxxxxxxxxxxxxxxxxx> wrote:

> On Thu, Jan 04, 2024 at 10:05:44AM -0500, Steven Rostedt wrote:
> 
> > 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.
> 
> > >  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.
> 
> > > 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.
> 
> > > 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).
> >   
> > > 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.

> 
> > > 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.

> 
> > 
> > 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




[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux