Subject: [bug#33893] [PATCH v5 2/4] gnu: Add docker-engine.

From: Danny Milosavljevic <[email-masked]>
Date: Thu, 10 Jan 2019 14:15:42 +0100

Hi Ludo,

On Thu, 10 Jan 2019 09:50:49 +0100
Ludovic Courtès <[email-masked]> wrote:

> Howdy!
> 
> Danny Milosavljevic <[email-masked]> skribis:
> 
> > On Tue, 08 Jan 2019 09:42:14 +0100
> > Ludovic Courtès <[email-masked]> wrote:
> >  
> >> > Go has peculiar ideas of how the directory layout is supposed to be set up.
> >> > I could probably figure it out - but if someone with more Go knowledge could
> >> > step forward it would be much faster.    
> >> 
> >> I see Leo is Cc’d so we’ll see.  :-)  
> >
> > Nevermind, I've fixed it and learned something in the process:
> >
> > Linux doesn't actually know the current working directory as a string.
> > It only knows the inode, so if you call getcwd, what libc actually does is
> > it opendirs "..", then finds the entry with the same inode number as
> > the current directory, and then returns the name of that entry.  

According to the POSIX standard ;)

> Are you sure?  In the Linux port of glibc I see this:
> 
> --8<---------------cut here---------------start------------->8---
> char *
> __getcwd (char *buf, size_t size)
> {
>   char *path;
>   char *result;
> 
>   // […]
> 
>   retval = INLINE_SYSCALL (getcwd, 2, path, alloc_size);
> --8<---------------cut here---------------end--------------->8---
> 
> And indeed, there’s a ‘getcwd’ syscall:
> 
> --8<---------------cut here---------------start------------->8---
> $ strace -e getcwd guile -c '(getcwd)'
> getcwd("/home/ludo", 100)               = 11
> +++ exited with 0 +++
> --8<---------------cut here---------------end--------------->8---

Huh.  I guess it boils down to whether the Linux "process" structure
has the cwd in it as a string or as an inode.

In Linux sources:

static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)
{
        spin_lock(&fs->lock);
        *pwd = fs->pwd;
        path_get(pwd);
        spin_unlock(&fs->lock);
}

static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root,
                                    struct path *pwd)
{
        unsigned seq;

        do {
                seq = read_seqcount_begin(&fs->seq);
                *root = fs->root;
                *pwd = fs->pwd;
        } while (read_seqcount_retry(&fs->seq, seq));
}

struct path {
        struct vfsmount *mnt;
        struct dentry *dentry;
} __randomize_layout;

SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
        int error;
        struct path pwd, root;
        char *page = __getname();

        if (!page)
                return -ENOMEM;

        rcu_read_lock();
        get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);

        error = -ENOENT;
        if (!d_unlinked(pwd.dentry)) {
                unsigned long len;
                char *cwd = page + PATH_MAX;
                int buflen = PATH_MAX;

                prepend(&cwd, &buflen, "\0", 1);
                error = prepend_path(&pwd, &root, &cwd, &buflen);
                rcu_read_unlock();

                if (error < 0)
                        goto out;

                /* Unreachable from current root */
                if (error > 0) {
                        error = prepend_unreachable(&cwd, &buflen);
                        if (error)
                                goto out;
                }

                error = -ERANGE;
                len = PATH_MAX + page - cwd;
                if (len <= size) {
                        error = len;
                        if (copy_to_user(buf, cwd, len))
                                error = -EFAULT;
                }
        } else {
                rcu_read_unlock();
        }

out:
        __putname(page);
        return error;
}

/*
 * Replace the fs->{pwdmnt,pwd} with {mnt,dentry}. Put the old values.
 * It can block.
 */
void set_fs_pwd(struct fs_struct *fs, const struct path *path)
{
        struct path old_pwd;

        path_get(path);
        spin_lock(&fs->lock);
        write_seqcount_begin(&fs->seq);
        old_pwd = fs->pwd;
        fs->pwd = *path; <----------------- !!!!
        write_seqcount_end(&fs->seq);
        spin_unlock(&fs->lock);

        if (old_pwd.dentry)
                path_put(&old_pwd);
}

int ksys_chdir(const char __user *filename)
{
        struct path path;
        int error;
        unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
        error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
        if (error)
                goto out;

        error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
        if (error)
                goto dput_and_out;

        set_fs_pwd(current->fs, &path); <----------------- !!!

dput_and_out:
        path_put(&path);
        if (retry_estale(error, lookup_flags)) {
                lookup_flags |= LOOKUP_REVAL;
                goto retry;
        }
out:
        return error;
}

SYSCALL_DEFINE1(chdir, const char __user *, filename)
{
        return ksys_chdir(filename);
}

SYSCALL_DEFINE1(fchdir, unsigned int, fd)
{
        struct fd f = fdget_raw(fd);
        int error;

        error = -EBADF;
        if (!f.file)
                goto out;

        error = -ENOTDIR;
        if (!d_can_lookup(f.file->f_path.dentry))
                goto out_putf;

        error = inode_permission(file_inode(f.file), MAY_EXEC | MAY_CHDIR);
        if (!error)
                set_fs_pwd(current->fs, &f.file->f_path);
out_putf:
        fdput(f);
out:
        return error;
}

Interesting!

> Thanks for taking the time to address this!

No problem :)