[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
euidaccessat [Re: one more openat-style function required: fchmodat
From: |
Jim Meyering |
Subject: |
euidaccessat [Re: one more openat-style function required: fchmodat |
Date: |
Thu, 05 Jan 2006 12:14:34 +0100 |
Roland McGrath <address@hidden> wrote:
> It's a natural, obvious, and useful addition, no matter what Solaris does
> or doesn't have. I've put it in.
Thanks for the speedy addition!
> If there are any more *at additions that
> might be useful, right now is the time to bring them up. So rack your brain.
In case I didn't mention it on this list before, euidaccessat is one
function that is required to implement a POSIX-conforming rm(1) based
on a chdir-free recursive-removal function.
Here is the relevant function from coreutils/src/remove.c.
If you can see a way to do this without a function like euidaccessat,
POSIX-conformance weenies using rm on deep ACL/xattr-protected trees
will thank you. FYI, this function serves only to determine whether
rm (without -f) should prompt the user before removing the file or
directory in question, so it's not a big deal either way.
/* Return true if FILE is determined to be an unwritable non-symlink.
Otherwise, return false (including when lstat'ing it fails).
If lstat (aka fstatat) succeeds, set *BUF_P to BUF.
This is to avoid calling euidaccess when FILE is a symlink. */
static bool
write_protected_non_symlink (int fd_cwd,
char const *file,
Dirstack_state const *ds,
struct stat **buf_p,
struct stat *buf)
{
if (fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0)
return false;
*buf_p = buf;
if (S_ISLNK (buf->st_mode))
return false;
/* Here, we know FILE is not a symbolic link. */
/* In order to be reentrant -- i.e., to avoid changing the working
directory, and at the same time to be able to deal with alternate
access control mechanisms (ACLs, xattr-style attributes) and
arbitrarily deep trees -- we need a function like eaccessat, i.e.,
like Solaris' eaccess, but fd-relative, in the spirit of openat. */
/* In the absence of a native eaccessat function, here are some of
the implementation choices [#4 and #5 were suggested by Paul Eggert]:
1) call openat with O_WRONLY|O_NOCTTY
Disadvantage: may create the file and doesn't work for directory,
may mistakenly report `unwritable' for EROFS or ACLs even though
perm bits say the file is writable.
2) fake eaccessat (save_cwd, fchdir, call euidaccess, restore_cwd)
Disadvantage: changes working directory (not reentrant) and can't
work if save_cwd fails.
3) if (euidaccess (full_filename (file), W_OK) == 0)
Disadvantage: doesn't work if full_filename is too long.
Inefficient for very deep trees (O(Depth^2)).
4) If the full pathname is sufficiently short (say, less than
PATH_MAX or 8192 bytes, whichever is shorter):
use method (3) (i.e., euidaccess (full_filename (file), W_OK));
Otherwise: vfork, fchdir in the child, run euidaccess in the
child, then the child exits with a status that tells the parent
whether euidaccess succeeded.
This avoids the O(N**2) algorithm of method (3), and it also avoids
the failure-due-to-too-long-file-names of method (3), but it's fast
in the normal shallow case. It also avoids the lack-of-reentrancy
and the save_cwd problems.
Disadvantage; it uses a process slot for very-long file names,
and would be very slow for hierarchies with many such files.
5) If the full file name is sufficiently short (say, less than
PATH_MAX or 8192 bytes, whichever is shorter):
use method (3) (i.e., euidaccess (full_filename (file), W_OK));
Otherwise: look just at the file bits. Perhaps issue a warning
the first time this occurs.
This is like (4), except for the "Otherwise" case where it isn't as
"perfect" as (4) but is considerably faster. It conforms to current
POSIX, and is uniformly better than what Solaris and FreeBSD do (they
mess up with long file names). */
{
/* This implements #5: */
size_t file_name_len
= obstack_object_size (&ds->dir_stack) + strlen (file);
return (file_name_len < MIN (PATH_MAX, 8192)
? euidaccess (full_filename (file), W_OK) != 0 && errno == EACCES
: euidaccess_stat (buf, W_OK) != 0);
}
}