| /** |
| * @file support/junction/junction.c |
| * @brief Common utilities for managing junctions on the local file system |
| */ |
| |
| /* |
| * Copyright 2010, 2018 Oracle. All rights reserved. |
| * |
| * This file is part of nfs-utils. |
| * |
| * nfs-utils is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2.0 as |
| * published by the Free Software Foundation. |
| * |
| * nfs-utils 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 General Public License version 2.0 for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * version 2.0 along with nfs-utils. If not, see: |
| * |
| * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <wchar.h> |
| #include <memory.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <dirent.h> |
| |
| #include <sys/xattr.h> |
| |
| #include "junction.h" |
| #include "junction-internal.h" |
| #include "xlog.h" |
| |
| /** |
| * Open a file system object |
| * |
| * @param pathname NUL-terminated C string containing pathname of an object |
| * @param fd OUT: a file descriptor number is filled in |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_open_path(const char *pathname, int *fd) |
| { |
| int tmp; |
| |
| if (pathname == NULL || fd == NULL) |
| return FEDFS_ERR_INVAL; |
| |
| tmp = open(pathname, O_DIRECTORY); |
| if (tmp == -1) { |
| switch (errno) { |
| case EPERM: |
| return FEDFS_ERR_ACCESS; |
| case EACCES: |
| return FEDFS_ERR_PERM; |
| default: |
| xlog(D_GENERAL, "%s: Failed to open path %s: %m", |
| __func__, pathname); |
| return FEDFS_ERR_INVAL; |
| } |
| } |
| |
| *fd = tmp; |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Predicate: is object a directory? |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_is_directory(int fd, const char *path) |
| { |
| struct stat stb; |
| |
| if (fstat(fd, &stb) == -1) { |
| xlog(D_GENERAL, "%s: failed to stat %s: %m", |
| __func__, path); |
| return FEDFS_ERR_ACCESS; |
| } |
| |
| if (!S_ISDIR(stb.st_mode)) { |
| xlog(D_CALL, "%s: %s is not a directory", |
| __func__, path); |
| return FEDFS_ERR_INVAL; |
| } |
| |
| xlog(D_CALL, "%s: %s is a directory", __func__, path); |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Predicate: is a directory's sticky bit set? |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_is_sticky_bit_set(int fd, const char *path) |
| { |
| struct stat stb; |
| |
| if (fstat(fd, &stb) == -1) { |
| xlog(D_GENERAL, "%s: failed to stat %s: %m", |
| __func__, path); |
| return FEDFS_ERR_ACCESS; |
| } |
| |
| if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) { |
| xlog(D_CALL, "%s: execute bit set on %s", |
| __func__, path); |
| return FEDFS_ERR_NOTJUNCT; |
| } |
| |
| if (!(stb.st_mode & S_ISVTX)) { |
| xlog(D_CALL, "%s: sticky bit not set on %s", |
| __func__, path); |
| return FEDFS_ERR_NOTJUNCT; |
| } |
| |
| xlog(D_CALL, "%s: sticky bit is set on %s", __func__, path); |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Set just a directory's sticky bit |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_set_sticky_bit(int fd, const char *path) |
| { |
| struct stat stb; |
| |
| if (fstat(fd, &stb) == -1) { |
| xlog(D_GENERAL, "%s: failed to stat %s: %m", |
| __func__, path); |
| return FEDFS_ERR_ACCESS; |
| } |
| |
| stb.st_mode &= (unsigned int)~ALLPERMS; |
| stb.st_mode |= S_ISVTX; |
| |
| if (fchmod(fd, stb.st_mode) == -1) { |
| xlog(D_GENERAL, "%s: failed to set sticky bit on %s: %m", |
| __func__, path); |
| return FEDFS_ERR_ROFS; |
| } |
| |
| xlog(D_CALL, "%s: set sticky bit on %s", __func__, path); |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Predicate: does a directory have an xattr named "name"? |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @param name NUL-terminated C string containing name of xattr to check |
| * @return a FedFsStatus code |
| * |
| * @note Access to trusted attributes requires CAP_SYS_ADMIN. |
| */ |
| FedFsStatus |
| junction_is_xattr_present(int fd, const char *path, const char *name) |
| { |
| ssize_t rc; |
| |
| /* |
| * Do not assume the total number of extended attributes |
| * this object may have. |
| */ |
| rc = fgetxattr(fd, name, NULL, 0); |
| if (rc == -1) { |
| switch (errno) { |
| case EPERM: |
| xlog(D_CALL, "%s: no access to xattr %s on %s", |
| __func__, name, path); |
| return FEDFS_ERR_PERM; |
| case ENODATA: |
| xlog(D_CALL, "%s: no xattr %s present on %s", |
| __func__, name, path); |
| return FEDFS_ERR_NOTJUNCT; |
| default: |
| xlog(D_CALL, "%s: xattr %s not found on %s: %m", |
| __func__, name, path); |
| return FEDFS_ERR_IO; |
| } |
| } |
| |
| xlog(D_CALL, "%s: xattr %s found on %s", |
| __func__, name, path); |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Read the contents of xattr "name" |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @param name NUL-terminated C string containing name of xattr to retrieve |
| * @param contents OUT: NUL-terminated C string containing contents of xattr |
| * @return a FedFsStatus code |
| * |
| * If junction_read_xattr() returns FEDFS_OK, the caller must free "*contents" |
| * with free(3). |
| * |
| * @note Access to trusted attributes requires CAP_SYS_ADMIN. |
| */ |
| FedFsStatus |
| junction_read_xattr(int fd, const char *path, const char *name, char **contents) |
| { |
| char *xattrbuf = NULL; |
| ssize_t len; |
| |
| len = fgetxattr(fd, name, xattrbuf, 0); |
| if (len < 0) { |
| xlog(D_GENERAL, "%s: failed to get size of xattr %s on %s: %m", |
| __func__, name, path); |
| return FEDFS_ERR_ACCESS; |
| } |
| |
| xattrbuf = malloc((size_t)len + 1); |
| if (xattrbuf == NULL) { |
| xlog(D_GENERAL, "%s: failed to get buffer for xattr %s on %s", |
| __func__, name, path); |
| return FEDFS_ERR_SVRFAULT; |
| } |
| |
| if (fgetxattr(fd, name, xattrbuf, (size_t)len) == -1) { |
| xlog(D_GENERAL, "%s: failed to get xattr %s on %s: %m", |
| __func__, name, path); |
| free(xattrbuf); |
| return FEDFS_ERR_ACCESS; |
| } |
| xattrbuf[len] = '\0'; |
| |
| xlog(D_CALL, "%s: read xattr %s from path %s", |
| __func__, name, path); |
| *contents = xattrbuf; |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Retrieve the contents of xattr "name" |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @param name NUL-terminated C string containing name of xattr to retrieve |
| * @param contents OUT: opaque byte array containing contents of xattr |
| * @param contentlen OUT: size of "contents" |
| * @return a FedFsStatus code |
| * |
| * If junction_get_xattr() returns FEDFS_OK, the caller must free "*contents" |
| * with free(3). |
| * |
| * @note Access to trusted attributes requires CAP_SYS_ADMIN. |
| */ |
| FedFsStatus |
| junction_get_xattr(int fd, const char *path, const char *name, void **contents, |
| size_t *contentlen) |
| { |
| void *xattrbuf = NULL; |
| ssize_t len; |
| |
| len = fgetxattr(fd, name, xattrbuf, 0); |
| if (len < 0) { |
| xlog(D_GENERAL, "%s: failed to get size of xattr %s on %s: %m", |
| __func__, name, path); |
| return FEDFS_ERR_ACCESS; |
| } |
| |
| xattrbuf = malloc((size_t)len); |
| if (xattrbuf == NULL) { |
| xlog(D_GENERAL, "%s: failed to get buffer for xattr %s on %s", |
| __func__, name, path); |
| return FEDFS_ERR_SVRFAULT; |
| } |
| |
| if (fgetxattr(fd, name, xattrbuf, (size_t)len) == -1) { |
| xlog(D_GENERAL, "%s: failed to get xattr %s on %s: %m", |
| __func__, name, path); |
| free(xattrbuf); |
| return FEDFS_ERR_ACCESS; |
| } |
| |
| xlog(D_CALL, "%s: read xattr %s from path %s", |
| __func__, name, path); |
| *contents = xattrbuf; |
| *contentlen = (size_t)len; |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Update the contents of an xattr |
| * |
| * @param fd an open file descriptor |
| * @param path NUL-terminated C string containing pathname of a directory |
| * @param name NUL-terminated C string containing name of xattr to set |
| * @param contents opaque byte array containing contents of xattr |
| * @param contentlen size of "contents" |
| * @return a FedFsStatus code |
| * |
| * The extended attribute is created if it does not exist. |
| * Its contents are replaced if it does. |
| * |
| * @note Access to trusted attributes requires CAP_SYS_ADMIN. |
| */ |
| FedFsStatus |
| junction_set_xattr(int fd, const char *path, const char *name, |
| const void *contents, const size_t contentlen) |
| { |
| /* |
| * XXX: Eventually should distinguish among several errors: |
| * object isn't there, no root access, some other issue |
| */ |
| if (fsetxattr(fd, name, contents, contentlen, 0) == -1) { |
| xlog(D_GENERAL, "%s: Failed to set xattr %s on %s: %m", |
| __func__, name, path); |
| return FEDFS_ERR_IO; |
| } |
| |
| xlog(D_CALL, "%s: Wrote xattr %s from path %s", |
| __func__, name, path); |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Remove one xattr |
| * |
| * @param fd an open file descriptor |
| * @param pathname NUL-terminated C string containing pathname of a directory |
| * @param name NUL-terminated C string containing name of xattr to set |
| * @return a FedFsStatus code |
| * |
| * @note Access to trusted attributes requires CAP_SYS_ADMIN. |
| */ |
| FedFsStatus |
| junction_remove_xattr(int fd, const char *pathname, const char *name) |
| { |
| /* |
| * XXX: Eventually should distinguish among several errors: |
| * object isn't there, no root access, some other issue |
| */ |
| if (fremovexattr(fd, name) == -1) { |
| xlog(D_GENERAL, "%s: failed to remove xattr %s from %s: %m", |
| __func__, name, pathname); |
| return FEDFS_ERR_ACCESS; |
| } |
| xlog(D_CALL, "%s: removed xattr %s from path %s", |
| __func__, name, pathname); |
| return FEDFS_OK; |
| } |
| |
| /** |
| * Retrieve object's mode bits. |
| * |
| * @param pathname NUL-terminated C string containing pathname of a directory |
| * @param mode OUT: mode bits |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_get_mode(const char *pathname, mode_t *mode) |
| { |
| FedFsStatus retval; |
| struct stat stb; |
| int fd; |
| |
| retval = junction_open_path(pathname, &fd); |
| if (retval != FEDFS_OK) |
| return retval; |
| |
| if (fstat(fd, &stb) == -1) { |
| xlog(D_GENERAL, "%s: failed to stat %s: %m", |
| __func__, pathname); |
| (void)close(fd); |
| return FEDFS_ERR_ACCESS; |
| } |
| (void)close(fd); |
| |
| xlog(D_CALL, "%s: pathname %s has mode %o", |
| __func__, pathname, stb.st_mode); |
| *mode = stb.st_mode; |
| return FEDFS_OK; |
| |
| } |
| |
| /** |
| * Save the object's mode in an xattr. Saved mode is human-readable. |
| * |
| * @param pathname NUL-terminated C string containing pathname of a directory |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_save_mode(const char *pathname) |
| { |
| FedFsStatus retval; |
| mode_t mode; |
| char buf[8]; |
| int fd; |
| |
| retval = junction_get_mode(pathname, &mode); |
| if (retval != FEDFS_OK) |
| return retval; |
| (void)snprintf(buf, sizeof(buf), "%o", ALLPERMS & mode); |
| |
| retval = junction_open_path(pathname, &fd); |
| if (retval != FEDFS_OK) |
| return retval; |
| |
| retval = junction_set_xattr(fd, pathname, JUNCTION_XATTR_NAME_MODE, |
| buf, strlen(buf)); |
| if (retval != FEDFS_OK) |
| goto out; |
| |
| retval = junction_set_sticky_bit(fd, pathname); |
| if (retval != FEDFS_OK) { |
| (void)junction_remove_xattr(fd, pathname, |
| JUNCTION_XATTR_NAME_MODE); |
| goto out; |
| } |
| |
| xlog(D_CALL, "%s: saved mode %o to %s", __func__, mode, pathname); |
| retval = FEDFS_OK; |
| |
| out: |
| (void)close(fd); |
| return retval; |
| |
| } |
| |
| /** |
| * Restore an object's mode bits |
| * |
| * @param pathname NUL-terminated C string containing pathname of a directory |
| * @return a FedFsStatus code |
| */ |
| FedFsStatus |
| junction_restore_mode(const char *pathname) |
| { |
| FedFsStatus retval; |
| char *buf = NULL; |
| mode_t mode; |
| int fd; |
| |
| retval = junction_open_path(pathname, &fd); |
| if (retval != FEDFS_OK) |
| return retval; |
| |
| retval = junction_read_xattr(fd, pathname, JUNCTION_XATTR_NAME_MODE, &buf); |
| if (retval != FEDFS_OK) |
| goto out; |
| |
| retval = FEDFS_ERR_SVRFAULT; |
| if (sscanf((char *)buf, "%o", &mode) != 1) { |
| xlog(D_GENERAL, "%s: failed to parse saved mode on %s", |
| __func__, pathname); |
| goto out; |
| } |
| |
| retval = FEDFS_ERR_ROFS; |
| if (fchmod(fd, mode) == -1) { |
| xlog(D_GENERAL, "%s: failed to set mode of %s to %o: %m", |
| __func__, pathname, mode); |
| goto out; |
| } |
| |
| xlog(D_CALL, "%s: restored mode %o to %s", __func__, mode, pathname); |
| retval = FEDFS_OK; |
| |
| out: |
| free(buf); |
| (void)close(fd); |
| return retval; |
| } |