files.c revision 48130eef6c5c64a07094b9e8582ba358b2048f24
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/*
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher Authors:
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher Jakub Hrozek <jhrozek@redhat.com>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher Copyright (C) 2009 Red Hat
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher This program is free software; you can redistribute it and/or modify
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher it under the terms of the GNU General Public License as published by
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher the Free Software Foundation; either version 3 of the License, or
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher (at your option) any later version.
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher This program is distributed in the hope that it will be useful,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher but WITHOUT ANY WARRANTY; without even the implied warranty of
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
ee359fe1384507fed6c2274e7bfe81d288de4542Stephen Gallagher GNU General Public License for more details.
33396dc46ea52c18f47db1b5d590880806521005Sumit Bose
ee359fe1384507fed6c2274e7bfe81d288de4542Stephen Gallagher You should have received a copy of the GNU General Public License
33396dc46ea52c18f47db1b5d590880806521005Sumit Bose along with this program. If not, see <http://www.gnu.org/licenses/>.
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher*/
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/*
84ae5edab16ad6be5e3be956cb6fa031c1428eb5Stephen Gallagher * This file incorporates work covered by the following copyright and
84ae5edab16ad6be5e3be956cb6fa031c1428eb5Stephen Gallagher * permission notice:
84ae5edab16ad6be5e3be956cb6fa031c1428eb5Stephen Gallagher *
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * Copyright (c) 1991 - 1994, Julianne Frances Haugh
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * Copyright (c) 1996 - 2001, Marek Michałkiewicz
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * Copyright (c) 2003 - 2006, Tomasz Kłoczko
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * Copyright (c) 2007 - 2008, Nicolas François
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher *
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * All rights reserved.
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher *
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * Redistribution and use in source and binary forms, with or without
84ae5edab16ad6be5e3be956cb6fa031c1428eb5Stephen Gallagher * modification, are permitted provided that the following conditions
cc98edd9479d4622634a1275c98058916c14059aStephen Gallagher * are met:
ee359fe1384507fed6c2274e7bfe81d288de4542Stephen Gallagher * 1. Redistributions of source code must retain the above copyright
cc98edd9479d4622634a1275c98058916c14059aStephen Gallagher * notice, this list of conditions and the following disclaimer.
1183d29d87c5c7439cf2364b7d7324d4a13b6e35Stephen Gallagher * 2. Redistributions in binary form must reproduce the above copyright
1183d29d87c5c7439cf2364b7d7324d4a13b6e35Stephen Gallagher * notice, this list of conditions and the following disclaimer in the
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * documentation and/or other materials provided with the distribution.
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * 3. The name of the copyright holders or contributors may not be used to
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * endorse or promote products derived from this software without
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * specific prior written permission.
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher *
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce */
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce#include "config.h"
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <sys/stat.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <sys/types.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <sys/time.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <dirent.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <fcntl.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <errno.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include <talloc.h>
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include "util/util.h"
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#include "tools/tools_util.h"
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstruct copy_ctx {
d921c1eba437662437847279f251a0a5d8f70127Maxim const char *src_orig;
d921c1eba437662437847279f251a0a5d8f70127Maxim const char *dst_orig;
d921c1eba437662437847279f251a0a5d8f70127Maxim dev_t src_dev;
d921c1eba437662437847279f251a0a5d8f70127Maxim uid_t uid;
d921c1eba437662437847279f251a0a5d8f70127Maxim gid_t gid;
d921c1eba437662437847279f251a0a5d8f70127Maxim};
d921c1eba437662437847279f251a0a5d8f70127Maxim
327127bb7fcc07f882209f029e14026de1b23c94Maximstatic int open_cloexec(const char *pathname, int flags, int *ret)
327127bb7fcc07f882209f029e14026de1b23c94Maxim{
327127bb7fcc07f882209f029e14026de1b23c94Maxim int fd;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int oflags;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher oflags = flags;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#ifdef O_CLOEXEC
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher oflags |= O_CLOEXEC;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#endif
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher errno = 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher fd = open(pathname, oflags);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (fd == -1) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (ret) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher *ret = errno;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher }
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return -1;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher }
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#ifndef O_CLOEXEC
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int v;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher v = fcntl(fd, F_GETFD, 0);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* we ignore an error, it's not fatal and there is nothing we
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * can do about it anyways */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher (void)fcntl(fd, F_SETFD, v | FD_CLOEXEC);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#endif
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return fd;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher}
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int openat_cloexec(int dir_fd, const char *pathname, int flags, int *ret)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher{
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int fd;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int oflags;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher oflags = flags;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#ifdef O_CLOEXEC
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher oflags |= O_CLOEXEC;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#endif
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher errno = 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher fd = openat(dir_fd, pathname, oflags);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (fd == -1) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (ret) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher *ret = errno;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher }
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return -1;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher }
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#ifndef O_CLOEXEC
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int v;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher v = fcntl(fd, F_GETFD, 0);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* we ignore an error, it's not fatal and there is nothing we
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher * can do about it anyways */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher (void)fcntl(fd, F_SETFD, v | FD_CLOEXEC);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#endif
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return fd;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher}
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int sss_timeat_set(int dir_fd, const char *path,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher const struct stat *statp,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int flags)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher{
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int ret;
6f51c802311fd81a409a26763ed45b28a3234d0dJakub Hrozek
6f51c802311fd81a409a26763ed45b28a3234d0dJakub Hrozek#ifdef HAVE_UTIMENSAT
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher struct timespec timebuf[2];
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher timebuf[0] = statp->st_atim;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher timebuf[1] = statp->st_mtim;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher ret = utimensat(dir_fd, path, timebuf, flags);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#else
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher struct timeval tv[2];
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[0].tv_sec = statp->st_atime;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[0].tv_usec = 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[1].tv_sec = statp->st_mtime;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[1].tv_usec = 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher ret = futimesat(dir_fd, path, tv);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#endif
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (ret == -1) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return errno;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher }
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return EOK;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher}
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int sss_futime_set(int fd, const struct stat *statp)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher{
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher int ret;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#ifdef HAVE_FUTIMENS
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher struct timespec timebuf[2];
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher timebuf[0] = statp->st_atim;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher timebuf[1] = statp->st_mtim;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher ret = futimens(fd, timebuf);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#else
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher struct timeval tv[2];
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[0].tv_sec = statp->st_atime;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[0].tv_usec = 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[1].tv_sec = statp->st_mtime;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher tv[1].tv_usec = 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher
96c73559adfbdac96720008fc022cb1d540b53c3Jakub Hrozek ret = futimes(fd, tv);
6f51c802311fd81a409a26763ed45b28a3234d0dJakub Hrozek#endif
84ae5edab16ad6be5e3be956cb6fa031c1428eb5Stephen Gallagher if (ret == -1) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return errno;
}
return EOK;
}
/* wrapper in order not to create a temporary context in
* every iteration */
static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx,
int parent_fd,
const char *dir_name,
dev_t parent_dev);
int remove_tree(const char *root)
{
TALLOC_CTX *tmp_ctx = NULL;
int ret;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
return ENOMEM;
}
ret = remove_tree_with_ctx(tmp_ctx, AT_FDCWD, root, 0);
talloc_free(tmp_ctx);
return ret;
}
/*
* The context is not freed in case of error
* because this is a recursive function, will be freed when we
* reach the top level remove_tree() again
*/
static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx,
int parent_fd,
const char *dir_name,
dev_t parent_dev)
{
struct dirent *result;
struct stat statres;
DIR *rootdir = NULL;
int ret, err;
int dir_fd;
dir_fd = openat_cloexec(parent_fd, dir_name,
O_RDONLY | O_DIRECTORY | O_NOFOLLOW, &ret);
if (dir_fd == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot open %s: [%d]: %s\n",
dir_name, ret, strerror(ret)));
return ret;
}
rootdir = fdopendir(dir_fd);
if (rootdir == NULL) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Cannot open directory: [%d][%s]\n", ret, strerror(ret)));
close(dir_fd);
goto fail;
}
while ((result = readdir(rootdir)) != NULL) {
if (strcmp(result->d_name, ".") == 0 ||
strcmp(result->d_name, "..") == 0) {
continue;
}
ret = fstatat(dir_fd, result->d_name,
&statres, AT_SYMLINK_NOFOLLOW);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("stat failed: [%d][%s]\n", ret, strerror(ret)));
goto fail;
}
if (S_ISDIR(statres.st_mode)) {
/* if directory, recursively descend, but check if on the same FS */
if (parent_dev && parent_dev != statres.st_dev) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Directory %s is on different filesystem, "
"will not follow\n"));
ret = EFAULT;
goto fail;
}
ret = remove_tree_with_ctx(mem_ctx, dir_fd, result->d_name, statres.st_dev);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Removing subdirectory failed: [%d][%s]\n",
ret, strerror(ret)));
goto fail;
}
} else {
ret = unlinkat(dir_fd, result->d_name, 0);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Removing file failed: [%d][%s]\n", ret, strerror(ret)));
goto fail;
}
}
}
ret = closedir(rootdir);
rootdir = NULL;
if (ret != 0) {
ret = errno;
goto fail;
}
ret = unlinkat(parent_fd, dir_name, AT_REMOVEDIR);
if (ret == -1) {
ret = errno;
}
ret = EOK;
fail:
if (rootdir) { /* clean up on abnormal exit but retain return code */
err = closedir(rootdir);
if (err) {
DEBUG(SSSDBG_CRIT_FAILURE, ("closedir failed, bad dirp?\n"));
}
}
return ret;
}
static char *talloc_readlinkat(TALLOC_CTX *mem_ctx, int dir_fd,
const char *filename)
{
size_t size = 1024;
ssize_t nchars;
char *buffer;
char *new_buffer;
buffer = talloc_array(mem_ctx, char, size);
if (!buffer) {
return NULL;
}
while (1) {
nchars = readlinkat(dir_fd, filename, buffer, size);
if (nchars < 0) {
talloc_free(buffer);
return NULL;
}
if ((size_t) nchars < size) {
/* The buffer was large enough */
break;
}
/* Try again with a bigger buffer */
size *= 2;
new_buffer = talloc_realloc(mem_ctx, buffer, char, size);
if (!new_buffer) {
talloc_free(buffer);
return NULL;
}
buffer = new_buffer;
}
/* readlink does not nul-terminate */
buffer[nchars] = '\0';
return buffer;
}
static int
copy_symlink(int src_dir_fd,
int dst_dir_fd,
const char *file_name,
const char *full_path,
const struct stat *statp,
uid_t uid, gid_t gid)
{
char *buf;
errno_t ret;
buf = talloc_readlinkat(NULL, src_dir_fd, file_name);
if (!buf) {
return ENOMEM;
}
ret = selinux_file_context(full_path);
if (ret != 0) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Failed to set SELinux context for [%s]\n", full_path));
/* Not fatal */
}
ret = symlinkat(buf, dst_dir_fd, file_name);
talloc_free(buf);
if (ret == -1) {
ret = errno;
if (ret == EEXIST) {
DEBUG(SSSDBG_MINOR_FAILURE,
("symlink pointing to already exists at '%s'\n", full_path));
return EOK;
}
DEBUG(SSSDBG_CRIT_FAILURE, ("symlinkat failed: %s\n", strerror(ret)));
return ret;
}
ret = fchownat(dst_dir_fd, file_name,
uid, gid, AT_SYMLINK_NOFOLLOW);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("fchownat failed: %s\n", strerror(ret)));
return ret;
}
ret = sss_timeat_set(dst_dir_fd, file_name, statp,
AT_SYMLINK_NOFOLLOW);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, ("utimensat failed [%d]: %s\n",
ret, strerror(ret)));
/* Do not fail */
}
return EOK;
}
/* Create a special file named file_name under a directory with file
* descriptor dst_dir_fd. full_path is used for both setting SELinux
* context and logging. The node is owned by uid/gid and its mode
* and device number is read from statp.
*/
static int copy_special(int dst_dir_fd,
const char *file_name,
const char *full_path,
const struct stat *statp,
uid_t uid, gid_t gid)
{
int ret;
ret = selinux_file_context(full_path);
if (ret != 0) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Failed to set SELinux context for [%s]\n", full_path));
/* Not fatal */
}
ret = mknodat(dst_dir_fd, file_name, statp->st_mode & ~07777,
statp->st_rdev);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_OP_FAILURE,
("Cannot mknod special file '%s': [%d][%s].\n",
full_path, ret, strerror(ret)));
return ret;
}
ret = fchownat(dst_dir_fd, file_name, uid, gid, 0);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("fchownat failed for '%s': [%d][%s]\n",
full_path, ret, strerror(ret)));
return ret;
}
ret = fchmodat(dst_dir_fd, file_name, statp->st_mode & 07777, 0);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("fchmodat failed for '%s': [%d][%s]\n",
full_path, ret, strerror(ret)));
return ret;
}
ret = sss_timeat_set(dst_dir_fd, file_name, statp, 0);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_MINOR_FAILURE,
("utimensat failed for '%s': [%d][%s]\n",
full_path, ret, strerror(ret)));
/* Do not fail, this shouldn't be fatal */
}
return EOK;
}
/* Copy bytes from input file descriptor ifd into file named
* dst_named under directory with dest_dir_fd. Own the new file
* by uid/gid
*/
static int
copy_file(int ifd,
int dest_dir_fd,
const char *file_name,
const char *full_path,
const struct stat *statp,
uid_t uid, gid_t gid)
{
int ofd = -1;
errno_t ret;
char buf[1024];
ssize_t cnt, written;
ret = selinux_file_context(full_path);
if (ret != 0) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Failed to set SELinux context for [%s]\n", full_path));
/* Not fatal */
}
/* Start with absolutely restrictive permissions */
ofd = openat(dest_dir_fd, file_name,
O_EXCL | O_CREAT | O_WRONLY | O_NOFOLLOW,
0);
if (ofd < 0 && errno != EEXIST) {
ret = errno;
DEBUG(SSSDBG_OP_FAILURE,
("Cannot open() destination file '%s': [%d][%s].\n",
full_path, ret, strerror(ret)));
goto done;
}
while ((cnt = sss_atomic_read_s(ifd, buf, sizeof(buf))) != 0) {
if (cnt == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Cannot read() from source file: [%d][%s].\n",
ret, strerror(ret)));
goto done;
}
errno = 0;
written = sss_atomic_write_s(ofd, buf, cnt);
if (written == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Cannot write() to destination file: [%d][%s].\n",
ret, strerror(ret)));
goto done;
}
if (written != cnt) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Wrote %d bytes, expected %d\n", written, cnt));
goto done;
}
}
/* Set the ownership; permissions are still
* restrictive. */
ret = fchown(ofd, uid, gid);
if (ret == -1 && errno != EPERM) {
ret = errno;
DEBUG(SSSDBG_OP_FAILURE,
("Error changing owner of '%s': %s\n",
full_path, strerror(ret)));
goto done;
}
/* Set the desired mode. */
ret = fchmod(ofd, statp->st_mode);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_OP_FAILURE, ("Error changing owner of '%s': %s\n",
full_path, strerror(ret)));
goto done;
}
ret = sss_futime_set(ofd, statp);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, ("sss_futime_set failed [%d]: %s\n",
ret, strerror(ret)));
/* Do not fail */
}
close(ofd);
ofd = -1;
ret = EOK;
done:
if (ofd != -1) close(ofd);
return ret;
}
static errno_t
copy_dir(struct copy_ctx *cctx,
int src_dir_fd, const char *src_dir_path,
int dest_parent_fd, const char *dest_dir_name,
const char *dest_dir_path,
mode_t mode,
const struct stat *src_dir_stat);
static errno_t
copy_entry(struct copy_ctx *cctx,
int src_dir_fd,
const char *src_dir_path,
int dest_dir_fd,
const char *dest_dir_path,
const char *ent_name)
{
char *src_ent_path = NULL;
char *dest_ent_path = NULL;
int ifd = -1;
errno_t ret;
struct stat st;
/* Build the path of the source file or directory and its
* corresponding member in the new tree. */
src_ent_path = talloc_asprintf(cctx, "%s/%s", src_dir_path, ent_name);
dest_ent_path = talloc_asprintf(cctx, "%s/%s", dest_dir_path, ent_name);
if (!src_ent_path || !dest_ent_path) {
ret = ENOMEM;
goto done;
}
/* Open the input entry first, then we can fstat() it and be
* certain that it is still the same file. O_NONBLOCK protects
* us against FIFOs and perhaps side-effects of the open() of a
* device file if there ever was one here, and doesn't matter
* for regular files or directories. */
ifd = openat_cloexec(src_dir_fd, ent_name,
O_RDONLY | O_NOFOLLOW | O_NONBLOCK, &ret);
if (ifd == -1 && ret != ELOOP) {
/* openat error */
DEBUG(SSSDBG_CRIT_FAILURE, ("openat failed on '%s': %s\n",
src_ent_path, strerror(ret)));
goto done;
} else if (ifd == -1 && ret == ELOOP) {
/* Should be a symlink.. */
ret = fstatat(src_dir_fd, ent_name, &st, AT_SYMLINK_NOFOLLOW);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE, ("fstatat failed on '%s': %s\n",
src_ent_path, strerror(ret)));
goto done;
}
/* Handle symlinks */
ret = copy_symlink(src_dir_fd, dest_dir_fd, ent_name,
dest_ent_path, &st, cctx->uid, cctx->gid);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Cannot copy '%s' to '%s'\n",
src_ent_path, dest_ent_path));
}
goto done;
}
ret = fstat(ifd, &st);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("couldn't stat '%s': %s", src_ent_path, strerror(ret)));
goto done;
}
if (S_ISDIR(st.st_mode)) {
/* If it's a directory, descend into it. */
ret = copy_dir(cctx, ifd, src_ent_path,
dest_dir_fd, ent_name,
dest_ent_path, st.st_mode & 07777,
&st);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
("Could recursively copy '%s' to '%s': %s\n",
src_ent_path, dest_dir_fd, strerror(ret)));
goto done;
}
} else if (S_ISREG(st.st_mode)) {
/* Copy a regular file */
ret = copy_file(ifd, dest_dir_fd, ent_name, dest_ent_path,
&st, cctx->uid, cctx->gid);
if (ret) {
DEBUG(SSSDBG_OP_FAILURE, ("Cannot copy '%s' to '%s'\n",
src_ent_path, dest_ent_path));
goto done;
}
} else {
/* Copy a special file */
ret = copy_special(dest_dir_fd, ent_name, dest_ent_path,
&st, cctx->uid, cctx->gid);
if (ret) {
DEBUG(SSSDBG_OP_FAILURE, ("Cannot copy '%s' to '%s'\n",
src_ent_path, dest_ent_path));
goto done;
}
}
ret = EOK;
done:
talloc_free(src_ent_path);
talloc_free(dest_ent_path);
if (ifd != -1) close(ifd);
return ret;
}
static errno_t
copy_dir(struct copy_ctx *cctx,
int src_dir_fd, const char *src_dir_path,
int dest_parent_fd, const char *dest_dir_name,
const char *dest_dir_path,
mode_t mode,
const struct stat *src_dir_stat)
{
errno_t ret;
int dest_dir_fd = -1;
DIR *dir = NULL;
struct dirent *ent;
if (!dest_dir_path) {
return EINVAL;
}
dir = fdopendir(src_dir_fd);
if (dir == NULL) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Error reading '%s': %s", src_dir_path, strerror(ret)));
goto done;
}
/* Create the directory. It starts owned by us (presumbaly root), with
* fairly restrictive permissions that still allow us to use the
* directory.
* */
errno = 0;
ret = mkdirat(dest_parent_fd, dest_dir_name, S_IRWXU);
if (ret == -1 && errno != EEXIST) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Error reading '%s': %s", dest_dir_path, strerror(ret)));
goto done;
}
dest_dir_fd = openat_cloexec(dest_parent_fd, dest_dir_name,
O_RDONLY | O_DIRECTORY | O_NOFOLLOW, &ret);
if (dest_dir_fd == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
("Error opening '%s': %s", dest_dir_path, strerror(ret)));
goto done;
}
while ((ent = readdir(dir)) != NULL) {
/* Iterate through each item in the directory. */
/* Skip over self and parent hard links. */
if (strcmp(ent->d_name, ".") == 0 ||
strcmp(ent->d_name, "..") == 0) {
continue;
}
ret = copy_entry(cctx,
src_dir_fd, src_dir_path,
dest_dir_fd, dest_dir_path,
ent->d_name);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Could not copy [%s] to [%s]\n",
src_dir_path, dest_dir_path));
goto done;
}
}
/* Set the ownership on the directory. Permissions are still
* fairly restrictive. */
ret = fchown(dest_dir_fd, cctx->uid, cctx->gid);
if (ret == -1 && errno != EPERM) {
ret = errno;
DEBUG(SSSDBG_OP_FAILURE,
("Error changing owner of '%s': %s",
dest_dir_path, strerror(ret)));
goto done;
}
/* Set the desired mode. Do this explicitly to preserve S_ISGID and
* other bits. Do this after chown, because chown is permitted to
* reset these bits. */
ret = fchmod(dest_dir_fd, mode);
if (ret == -1) {
DEBUG(SSSDBG_OP_FAILURE,
("Error setting mode of '%s': %s",
dest_dir_path, strerror(ret)));
goto done;
}
sss_futime_set(dest_dir_fd, src_dir_stat);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, ("sss_futime_set failed [%d]: %s\n",
ret, strerror(ret)));
/* Do not fail */
}
ret = EOK;
done:
if (dir) closedir(dir);
if (dest_dir_fd != -1) {
close(dest_dir_fd);
}
return ret;
}
/* NOTE:
* For several reasons, including the fact that we copy even special files
* (pipes, etc) from the skeleton directory, the skeldir needs to be trusted
*/
int copy_tree(const char *src_root, const char *dst_root,
mode_t mode_root, uid_t uid, gid_t gid)
{
int ret = EOK;
struct copy_ctx *cctx = NULL;
int fd = -1;
struct stat s_src;
fd = open_cloexec(src_root, O_RDONLY | O_DIRECTORY, &ret);
if (fd == -1) {
goto fail;
}
ret = fstat(fd, &s_src);
if (ret == -1) {
ret = errno;
goto fail;
}
cctx = talloc_zero(NULL, struct copy_ctx);
if (!cctx) {
ret = ENOMEM;
goto fail;
}
cctx->src_orig = src_root;
cctx->dst_orig = dst_root;
cctx->src_dev = s_src.st_dev;
cctx->uid = uid;
cctx->gid = gid;
ret = copy_dir(cctx, fd, src_root, AT_FDCWD,
dst_root, dst_root, mode_root, &s_src);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
("copy_dir failed: [%d][%s]\n", ret, strerror(ret)));
goto fail;
}
fail:
if (fd != -1) close(fd);
reset_selinux_file_context();
talloc_free(cctx);
return ret;
}