blob: a7e8b2595c7c53d86b0a1c72b977b7586b807097 [file] [edit]
/* A more POSIX-compliant lchown
Copyright (C) 1998-1999, 2002, 2004, 2006-2007, 2009-2026 Free Software
Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* written by Jim Meyering */
#include <config.h>
/* Specification. */
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include "issymlink.h"
#include "stat-time.h"
#ifndef CHOWN_CHANGE_TIME_BUG
# define CHOWN_CHANGE_TIME_BUG 0
#endif
#ifndef CHOWN_MODIFIES_SYMLINK
# define CHOWN_MODIFIES_SYMLINK 0
#endif
#ifndef CHOWN_TRAILING_SLASH_BUG
# define CHOWN_TRAILING_SLASH_BUG 0
#endif
/* Gnulib target platforms lacking utimensat do not need it,
because in practice the bug it works around does not occur. */
#if !HAVE_UTIMENSAT
# undef utimensat
# define utimensat(fd, file, times, flag) \
((void) (fd), (void) (file), (void) (times), (void) (flag), \
0)
#endif
#if !HAVE_LCHOWN
/* If the system chown does not follow symlinks, we don't want it
replaced by gnulib's chown, which does follow symlinks. */
# if CHOWN_MODIFIES_SYMLINK /* native Windows and some very old platforms */
# undef chown
# endif
/* Work just like chown, except when FILE is a symbolic link.
In that case, set errno to EOPNOTSUPP and return -1.
But if autoconf tests determined that chown modifies
symlinks, then just call chown. */
int
lchown (_GL_UNUSED char const *file, _GL_UNUSED uid_t owner,
_GL_UNUSED gid_t group)
{
# if HAVE_CHOWN
if (!CHOWN_MODIFIES_SYMLINK && 0 < issymlink (file))
{
errno = EOPNOTSUPP;
return -1;
}
return chown (file, owner, group);
# else /* !HAVE_CHOWN */
errno = ENOSYS;
return -1;
# endif
}
#else /* HAVE_LCHOWN */
/* Below we refer to the system's function. */
# undef lchown
/* Provide a more-closely POSIX-conforming version. */
int
rpl_lchown (const char *file, uid_t owner, gid_t group)
{
gid_t no_gid = -1;
uid_t no_uid = -1;
bool gid_noop = group == no_gid;
bool uid_noop = owner == no_uid;
bool change_time_check = CHOWN_CHANGE_TIME_BUG && !(gid_noop & uid_noop);
struct stat st;
if (change_time_check
|| (CHOWN_TRAILING_SLASH_BUG
&& file[0] && file[strlen (file) - 1] == '/'))
{
bool file_is_symlink = false;
int r = lstat (file, &st);
if (0 <= r)
file_is_symlink = !!S_ISLNK (st.st_mode);
else if (errno != EOVERFLOW)
return r;
else
{
int s = issymlink (file);
if (s < 0)
return s;
if (0 < s)
{
errno = EOVERFLOW;
return -1;
}
/* FILE exists and is not a symbolic link; ST is unset.
Rely on Gnulib chown to work around platform chown bugs. */
}
if (!file_is_symlink)
return chown (file, owner, group);
}
int result = lchown (file, owner, group);
/* If no change in ownership, but at least one argument was not -1,
update ctime indirectly via a no-change update to atime and mtime.
Do not use UTIME_NOW or UTIME_OMIT as they might run into bugs
on some platforms. Do not communicate any failure to the caller
as that would be worse than communicating the ownership change. */
if (result == 0 && change_time_check
&& (((owner == st.st_uid) | uid_noop)
& ((group == st.st_gid) | gid_noop)))
{
struct timespec times[2];
times[0] = get_stat_atime (&st);
times[1] = get_stat_mtime (&st);
utimensat (AT_FDCWD, file, times, AT_SYMLINK_NOFOLLOW);
}
return result;
}
#endif /* HAVE_LCHOWN */