/*
* ProFTPD - FTP server daemon
* Copyright (c) 1997, 1998 Public Flood Software
* Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
* Copyright (C) 2001-2005 The ProFTPD Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
* and other respective copyright holders give permission to link this program
* with OpenSSL, and distribute the resulting executable, without including
* the source code for OpenSSL in the source distribution.
*/
/* ProFTPD virtual/modular file-system support
* $Id: fsio.c,v 1.49 2005/10/31 17:39:59 castaglia Exp $
*/
#include "conf.h"
#ifdef HAVE_REGEX_H
# include <regex.h>
#endif
#ifdef HAVE_SYS_STATVFS_H
# include <sys/statvfs.h>
#elif defined(HAVE_SYS_VFS_H)
# include <sys/vfs.h>
#elif defined(HAVE_SYS_MOUNT_H)
# include <sys/mount.h>
#endif
#ifdef AIX3
# include <sys/statfs.h>
#endif
#ifdef HAVE_ACL_LIBACL_H
# include <acl/libacl.h>
#endif
typedef struct fsopendir fsopendir_t;
struct fsopendir {
fsopendir_t *next,*prev;
/* pool for this object's use */
pool *pool;
pr_fs_t *fsdir;
DIR *dir;
};
static pr_fs_t *root_fs = NULL, *fs_cwd = NULL;
static array_header *fs_map = NULL;
#ifdef PR_FS_MATCH
static pr_fs_match_t *fs_match_list = NULL;
#endif /* PR_FS_MATCH */
static fsopendir_t *fsopendir_list;
static void *fs_cache_dir = NULL;
static pr_fs_t *fs_cache_fsdir = NULL;
/* Internal flag set whenever a new pr_fs_t has been added or removed, and
* cleared once the fs_map has been scanned
*/
static unsigned char chk_fs_map = FALSE;
/* Virtual working directory */
static char vwd[PR_TUNABLE_PATH_MAX + 1] = "/";
static char cwd[PR_TUNABLE_PATH_MAX + 1] = "/";
/* The following static functions are simply wrappers for system functions
*/
static int sys_stat(pr_fs_t *fs, const char *path, struct stat *sbuf) {
return stat(path, sbuf);
}
static int sys_fstat(pr_fh_t *fh, int fd, struct stat *sbuf) {
return fstat(fd, sbuf);
}
static int sys_lstat(pr_fs_t *fs, const char *path, struct stat *sbuf) {
return lstat(path, sbuf);
}
static int sys_rename(pr_fs_t *fs, const char *rnfm, const char *rnto) {
return rename(rnfm, rnto);
}
static int sys_unlink(pr_fs_t *fs, const char *path) {
return unlink(path);
}
static int sys_open(pr_fh_t *fh, const char *path, int flags) {
#ifdef CYGWIN
/* On Cygwin systems, we need the open(2) equivalent of fopen(3)'s "b"
* option. Cygwin defines an O_BINARY flag for this purpose.
*/
flags |= O_BINARY;
#endif
return open(path, flags, PR_OPEN_MODE);
}
static int sys_creat(pr_fh_t *fh, const char *path, mode_t mode) {
return creat(path, mode);
}
static int sys_close(pr_fh_t *fh, int fd) {
return close(fd);
}
static int sys_read(pr_fh_t *fh, int fd, char *buf, size_t size) {
return read(fd, buf, size);
}
static int sys_write(pr_fh_t *fh, int fd, const char *buf, size_t size) {
return write(fd, buf, size);
}
static off_t sys_lseek(pr_fh_t *fh, int fd, off_t offset, int whence) {
return lseek(fd, offset, whence);
}
static int sys_link(pr_fs_t *fs, const char *path1, const char *path2) {
return link(path1, path2);
}
static int sys_symlink(pr_fs_t *fs, const char *path1, const char *path2) {
return symlink(path1, path2);
}
static int sys_readlink(pr_fs_t *fs, const char *path, char *buf,
size_t buflen) {
return readlink(path, buf, buflen);
}
static int sys_ftruncate(pr_fh_t *fh, int fd, off_t len) {
return ftruncate(fd, len);
}
static int sys_truncate(pr_fs_t *fs, const char *path, off_t len) {
return truncate(path, len);
}
static int sys_chmod(pr_fs_t *fs, const char *path, mode_t mode) {
return chmod(path, mode);
}
static int sys_chown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
return chown(path, uid, gid);
}
/* We provide our own equivalent of access(2) here, rather than using
* access(2) directly, because access(2) uses the real IDs, rather than
* the effective IDs, of the process.
*/
static int sys_access(pr_fs_t *fs, const char *path, int mode, uid_t uid,
gid_t gid, array_header *suppl_gids) {
mode_t mask;
struct stat st;
pr_fs_clear_cache();
if (pr_fsio_stat(path, &st) < 0)
return -1;
/* Root always succeeds. */
if (uid == PR_ROOT_UID)
return 0;
/* Initialize mask to reflect the permission bits that are applicable for
* the given user. mask contains the user-bits if the user ID equals the
* ID of the file owner. mask contains the group bits if the group ID
* belongs to the group of the file. mask will always contain the other
* bits of the permission bits.
*/
mask = S_IROTH|S_IWOTH|S_IXOTH;
if (st.st_uid == uid)
mask |= S_IRUSR|S_IWUSR|S_IXUSR;
/* Check the current group, as well as all supplementary groups.
* Fortunately, we have this information cached, so accessing it is
* almost free.
*/
if (st.st_gid == gid) {
mask |= S_IRGRP|S_IWGRP|S_IXGRP;
} else {
if (suppl_gids) {
register unsigned int i = 0;
for (i = 0; i < suppl_gids->nelts; i++) {
if (st.st_gid == ((gid_t *) suppl_gids->elts)[i]) {
mask |= S_IRGRP|S_IWGRP|S_IXGRP;
break;
}
}
}
}
mask &= st.st_mode;
/* Perform requested access checks. */
if (mode & R_OK) {
if (!(mask & (S_IRUSR|S_IRGRP|S_IROTH))) {
errno = EACCES;
return -1;
}
}
if (mode & W_OK) {
if (!(mask & (S_IWUSR|S_IWGRP|S_IWOTH))) {
errno = EACCES;
return -1;
}
}
if (mode & X_OK) {
if (!(mask & (S_IXUSR|S_IXGRP|S_IXOTH))) {
errno = EACCES;
return -1;
}
}
/* F_OK already checked by checking the return value of stat. */
return 0;
}
static int sys_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid,
array_header *suppl_gids) {
return sys_access(fh->fh_fs, fh->fh_path, mode, uid, gid, suppl_gids);
}
static int sys_chroot(pr_fs_t *fs, const char *path) {
if (chroot(path) < 0)
return -1;
session.chroot_path = (char *) path;
return 0;
}
static int sys_chdir(pr_fs_t *fs, const char *path) {
if (chdir(path) < 0)
return -1;
pr_fs_setcwd(path);
return 0;
}
static void *sys_opendir(pr_fs_t *fs, const char *path) {
return opendir(path);
}
static int sys_closedir(pr_fs_t *fs, void *dir) {
return closedir((DIR *) dir);
}
static struct dirent *sys_readdir(pr_fs_t *fs, void *dir) {
return readdir((DIR *) dir);
}
static int sys_mkdir(pr_fs_t *fs, const char *path, mode_t mode) {
return mkdir(path, mode);
}
static int sys_rmdir(pr_fs_t *fs, const char *path) {
return rmdir(path);
}
static int fs_cmp(const void *a, const void *b) {
pr_fs_t *fsa, *fsb;
fsa = *((pr_fs_t **) a);
fsb = *((pr_fs_t **) b);
return strcmp(fsa->fs_path, fsb->fs_path);
}
/* Statcache stuff */
typedef struct {
char sc_path[PR_TUNABLE_PATH_MAX+1];
struct stat sc_stat;
int sc_errno;
} fs_statcache_t;
static fs_statcache_t statcache;
#define fs_cache_lstat(f, p, s) cache_stat((f), (p), (s), FSIO_FILE_LSTAT)
#define fs_cache_stat(f, p, s) cache_stat((f), (p), (s), FSIO_FILE_STAT)
static int cache_stat(pr_fs_t *fs, const char *path, struct stat *sbuf,
unsigned int op) {
int res = -1;
char pathbuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
int (*mystat)(pr_fs_t *, const char *, struct stat *) = NULL;
/* Sanity checks */
if (!fs) {
errno = EINVAL;
return -1;
}
if (!path) {
errno = ENOENT;
return -1;
}
/* Use only absolute path names. Construct them, if given a relative
* path, based on cwd. This obviates the need for something like
* realpath(3), which only introduces more stat system calls.
*/
if (*path != '/') {
sstrcat(pathbuf, cwd, sizeof(pathbuf)-1);
/* If the cwd is "/", we don't need to duplicate the path separator.
* On some systems (e.g. Cygwin), this duplication can cause problems,
* as the path may then have different semantics.
*/
if (strcmp(cwd, "/") != 0)
sstrcat(pathbuf, "/", sizeof(pathbuf)-1);
sstrcat(pathbuf, path, sizeof(pathbuf)-1);
} else
sstrncpy(pathbuf, path, sizeof(pathbuf)-1);
/* Determine which filesystem function to use, stat() or lstat() */
if (op == FSIO_FILE_STAT) {
mystat = fs->stat ? fs->stat : sys_stat;
} else {
mystat = fs->lstat ? fs->lstat : sys_lstat;
}
/* Can the last cached stat be used? */
if (strcmp(pathbuf, statcache.sc_path) == 0) {
/* Update the given struct stat pointer with the cached info */
memcpy(sbuf, &statcache.sc_stat, sizeof(struct stat));
/* Use the cached errno as well */
errno = statcache.sc_errno;
return 0;
}
res = mystat(fs, pathbuf, sbuf);
/* Update the cache */
memset(statcache.sc_path, '\0', sizeof(statcache.sc_path));
sstrncpy(statcache.sc_path, pathbuf, sizeof(statcache.sc_path));
memcpy(&statcache.sc_stat, sbuf, sizeof(struct stat));
statcache.sc_errno = errno;
return res;
}
/* Lookup routines */
/* Necessary prototype for static function */
static pr_fs_t *lookup_file_canon_fs(const char *, char **, int);
/* lookup_dir_fs() is called when we want to perform some sort of directory
* operation on a directory or file. A "closest" match algorithm is used. If
* the lookup fails or is not "close enough" (i.e. the final target does not
* exactly match an existing filesystem handle) scan the list of fs_matches for
* matchable targets and call any callback functions, then rescan the pr_fs_t
* list. The rescan is performed in case any modules registered pr_fs_ts
* during the hit.
*/
static pr_fs_t *lookup_dir_fs(const char *path, int op) {
char buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
char tmp_path[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
pr_fs_t *fs = NULL;
int exact = FALSE;
#ifdef PR_FS_MATCH
pr_fs_match_t *fsm = NULL;
#endif /* PR_FS_MATCH */
sstrncpy(buf, path, sizeof(buf));
/* Check if the given path is an absolute path. Since there may be
* alternate fs roots, this is not a simple check. If the path is
* not absolute, prepend the current location.
*/
if (pr_fs_valid_path(path) < 0) {
if (pr_fs_dircat(tmp_path, sizeof(tmp_path), cwd, buf) < 0)
return NULL;
} else
sstrncpy(tmp_path, buf, sizeof(tmp_path));
/* Make sure that if this is a directory operation, the path being
* search ends in a trailing slash -- this is how files and directories
* are differentiated in the fs_map.
*/
if ((FSIO_DIR_COMMON & op) && tmp_path[strlen(tmp_path) - 1] != '/')
sstrcat(tmp_path, "/", sizeof(tmp_path));
fs = pr_get_fs(tmp_path, &exact);
#ifdef PR_FS_MATCH
/* NOTE: what if there is a perfect matching pr_fs_t for the given path,
* but an fs_match with pattern of "." is registered? At present, that
* fs_match will never trigger...hmmm...OK. fs_matches are only scanned
* if and only if there is *not* an exactly matching pr_fs_t.
*
* NOTE: this is experimental code, not yet ready for module consumption.
* It was present in the older FS code, hence it's presence now.
*/
/* Is the returned pr_fs_t "close enough"? */
if (!fs || !exact) {
/* Look for an fs_match */
fsm = pr_get_fs_match(tmp_path, op);
while (fsm) {
/* Invoke the fs_match's callback function, if set
*
* NOTE: what pr_fs_t is being passed to the trigger??
*/
if (fsm->trigger) {
if (fsm->trigger(fs, tmp_path, op) <= 0)
pr_log_pri(PR_LOG_DEBUG, "error: fs_match '%s' trigger failed",
fsm->name);
}
/* Get the next matching fs_match */
fsm = pr_get_next_fs_match(fsm, tmp_path, op);
}
}
/* Now, check for a new pr_fs_t, if any were registered by fs_match
* callbacks. This time, it doesn't matter if it's an exact match --
* any pr_fs_t will do.
*/
if (chk_fs_map)
fs = pr_get_fs(tmp_path, &exact);
#endif /* PR_FS_MATCH */
return (fs ? fs : root_fs);
}
/* lookup_file_fs() performs the same function as lookup_dir_fs, however
* because we are performing a file lookup, the target is the subdirectory
* _containing_ the actual target. A basic optimization is used here,
* if the path contains no '/' characters, fs_cwd is returned.
*/
static pr_fs_t *lookup_file_fs(const char *path, char **deref, int op) {
if (!strchr(path, '/')) {
#ifdef PR_FS_MATCH
pr_fs_match_t *fsm = NULL;
fsm = pr_get_fs_match(path, op);
if (!fsm || fsm->trigger(fs_cwd, path, op) <= 0) {
#else
if (1) {
#endif /* PR_FS_MATCH */
pr_fs_t *fs = fs_cwd;
struct stat sbuf;
int (*mystat)(pr_fs_t *, const char *, struct stat *) = NULL;
/* Determine which function to use, stat() or lstat(). */
if (op == FSIO_FILE_STAT) {
while (fs && fs->fs_next && !fs->stat)
fs = fs->fs_next;
mystat = fs->stat;
} else {
while (fs && fs->fs_next && !fs->lstat)
fs = fs->fs_next;
mystat = fs->lstat;
}
if (mystat(fs, path, &sbuf) == -1 || !S_ISLNK(sbuf.st_mode))
return fs;
} else {
/* The given path is a symbolic link, in which case we need to find
* the actual path referenced, and return an pr_fs_t for _that_ path
*/
char linkbuf[PR_TUNABLE_PATH_MAX + 1];
int i;
/* Three characters are reserved at the end of linkbuf for some path
* characters (and a trailing NUL).
*/
i = pr_fsio_readlink(path, &linkbuf[2], sizeof(linkbuf)-3);
if (i != -1) {
linkbuf[i] = '\0';
if (strchr(linkbuf, '/') == NULL) {
if (i + 3 > PR_TUNABLE_PATH_MAX)
i = PR_TUNABLE_PATH_MAX - 3;
memmove(&linkbuf[2], linkbuf, i + 1);
linkbuf[i+2] = '\0';
linkbuf[0] = '.';
linkbuf[1] = '/';
return lookup_file_canon_fs(linkbuf, deref, op);
}
}
/* What happens if fs_cwd->readlink is NULL, or readlink() returns -1?
* I guess, for now, we punt, and return fs_cwd.
*/
return fs_cwd;
}
}
return lookup_dir_fs(path, op);
}
static pr_fs_t *lookup_file_canon_fs(const char *path, char **deref, int op) {
static char workpath[PR_TUNABLE_PATH_MAX + 1];
memset(workpath,'\0',sizeof(workpath));
if (pr_fs_resolve_partial(path, workpath, sizeof(workpath)-1,
FSIO_FILE_OPEN) == -1) {
if (*path == '/' || *path == '~') {
if (pr_fs_interpolate(path, workpath, sizeof(workpath)-1) != -1)
sstrncpy(workpath, path, sizeof(workpath));
} else {
if (pr_fs_dircat(workpath, sizeof(workpath), cwd, path) < 0)
return NULL;
}
}
if (deref)
*deref = workpath;
return lookup_file_fs(workpath, deref, op);
}
/* FS functions proper */
void pr_fs_clear_cache(void) {
memset(&statcache, '\0', sizeof(statcache));
}
int pr_fs_copy_file(const char *src, const char *dst) {
pr_fh_t *src_fh, *dst_fh;
struct stat st;
char *buf;
size_t bufsz;
int res;
src_fh = pr_fsio_open(src, O_RDONLY);
if (!src_fh) {
pr_log_pri(PR_LOG_WARNING, "error opening source file '%s' "
"for copying: %s", src, strerror(errno));
return -1;
}
/* Do not allow copying of directories. open(2) may not fail when
* opening the source path, since it is only doing a read-only open,
* which does work on directories.
*/
/* This should never fail. */
(void) pr_fsio_fstat(src_fh, &st);
if (S_ISDIR(st.st_mode)) {
pr_fsio_close(src_fh);
errno = EISDIR;
pr_log_pri(PR_LOG_WARNING, "warning: cannot copy source '%s': %s", src,
strerror(errno));
return -1;
}
dst_fh = pr_fsio_open(dst, O_WRONLY|O_CREAT);
if (!dst_fh) {
int xerrno = errno;
pr_fsio_close(src_fh);
errno = xerrno;
pr_log_pri(PR_LOG_WARNING, "error opening destination file '%s' "
"for copying: %s", dst, strerror(errno));
return -1;
}
/* Stat the source file to find its optimal copy block size. */
if (pr_fsio_fstat(src_fh, &st) < 0) {
int xerrno = errno;
pr_log_pri(PR_LOG_WARNING, "error checking source file '%s' "
"for copying: %s", src, strerror(errno));
pr_fsio_close(src_fh);
pr_fsio_close(dst_fh);
pr_fsio_unlink(dst);
errno = xerrno;
return -1;
}
bufsz = st.st_blksize;
buf = malloc(bufsz);
if (!buf) {
pr_log_pri(PR_LOG_CRIT, "Out of memory!");
end_login(1);
}
/* Make sure the destination file starts with a zero size. */
pr_fsio_truncate(dst, 0);
while ((res = pr_fsio_read(src_fh, buf, bufsz)) > 0) {
if (pr_fsio_write(dst_fh, buf, res) != res) {
pr_log_pri(PR_LOG_WARNING, "error copying to '%s': %s", dst,
strerror(errno));
break;
}
pr_signals_handle();
}
free(buf);
#if defined(HAVE_POSIX_ACL) && defined(PR_USE_FACL)
{
/* Copy any ACLs from the source file to the destination file as well. */
# if defined(HAVE_BSD_POSIX_ACL)
acl_t facl, facl_dup = NULL;
int have_facl = FALSE, have_dup = FALSE;
facl = acl_get_fd(PR_FH_FD(src_fh));
if (facl)
have_facl = TRUE;
if (have_facl)
facl_dup = acl_dup(facl);
if (facl_dup)
have_dup = TRUE;
if (have_dup &&
acl_set_fd(PR_FH_FD(dst_fh), facl_dup) < 0)
pr_log_debug(DEBUG3, "error applying ACL to destination file: %s",
strerror(errno));
if (have_dup)
acl_free(facl_dup);
# elif defined(HAVE_LINUX_POSIX_ACL)
/* Linux provides the handy perm_copy_fd(3) function in its libacl
* library just for this purpose.
*/
if (perm_copy_fd(src, PR_FH_FD(src_fh), dst, PR_FH_FD(dst_fh), NULL) < 0)
pr_log_debug(DEBUG3, "error copying ACL to destination file: %s",
strerror(errno));
# elif defined(HAVE_SOLARIS_POSIX_ACL)
int nents;
nents = facl(PR_FH_FD(src_fh), GETACLCNT, 0, NULL);
if (nents < 0)
pr_log_debug(DEBUG3, "error getting source file ACL count: %s",
strerror(errno));
else {
aclent_t *acls;
acls = malloc(sizeof(aclent_t) * nents);
if (!acls) {
pr_log_pri(PR_LOG_CRIT, "Out of memory!");
end_login(1);
}
if (facl(PR_FH_FD(src_fh), GETACL, nents, acls) < 0)
pr_log_debug(DEBUG3, "error getting source file ACLs: %s",
strerror(errno));
else {
if (facl(PR_FH_FD(dst_fh), SETACL, nents, acls) < 0)
pr_log_debug(DEBUG3, "error setting dest file ACLs: %s",
strerror(errno));
}
free(acls);
}
# endif /* HAVE_SOLARIS_POSIX_ACL && PR_USE_FACL */
}
#endif /* HAVE_POSIX_ACL */
pr_fsio_close(src_fh);
if (pr_fsio_close(dst_fh) < 0)
pr_log_pri(PR_LOG_WARNING, "error closing '%s': %s", dst,
strerror(errno));
return res;
}
pr_fs_t *pr_register_fs(pool *p, const char *name, const char *path) {
pr_fs_t *fs = NULL;
/* Sanity check */
if (!p || !name || !path) {
errno = EINVAL;
return NULL;
}
/* Instantiate an pr_fs_t */
fs = pr_create_fs(p, name);
if (fs != NULL) {
/* Call pr_insert_fs() from here */
if (!pr_insert_fs(fs, path)) {
pr_log_debug(DEBUG8, "FS: error inserting fs '%s' at path '%s'",
name, path);
destroy_pool(fs->fs_pool);
return NULL;
}
} else
pr_log_debug(DEBUG8, "FS: error creating fs '%s'", name);
return fs;
}
pr_fs_t *pr_create_fs(pool *p, const char *name) {
pr_fs_t *fs = NULL;
pool *fs_pool = NULL;
/* Sanity check */
if (!p || !name) {
errno = EINVAL;
return NULL;
}
/* Allocate a subpool, then allocate an pr_fs_t object from that subpool */
fs_pool = make_sub_pool(p);
pr_pool_tag(fs_pool, "FS Pool");
fs = pcalloc(fs_pool, sizeof(pr_fs_t));
if (!fs)
return NULL;
fs->fs_pool = fs_pool;
fs->fs_next = fs->fs_prev = NULL;
fs->fs_name = pstrdup(fs->fs_pool, name);
fs->fs_next = root_fs;
/* This is NULL until set by pr_insert_fs() */
fs->fs_path = NULL;
return fs;
}
int pr_insert_fs(pr_fs_t *fs, const char *path) {
char cleaned_path[PR_TUNABLE_PATH_MAX] = {'\0'};
if (!fs_map) {
pool *map_pool = make_sub_pool(permanent_pool);
pr_pool_tag(map_pool, "FSIO Map Pool");
fs_map = make_array(map_pool, 0, sizeof(pr_fs_t *));
}
/* Clean the path, but only if it starts with a '/'. Non-local-filesystem
* paths may not want/need to be cleaned.
*/
if (*path == '/') {
pr_fs_clean_path(path, cleaned_path, sizeof(cleaned_path));
/* Cleaning the path may have removed a trailing slash, which the
* caller may actually have wanted. Make sure one is present in
* the cleaned version, if it was present in the original version and
* is not present in the cleaned version.
*/
if (path[strlen(path)-1] == '/') {
size_t len = strlen(cleaned_path);
if (len > 1 &&
len < (PR_TUNABLE_PATH_MAX-3) &&
cleaned_path[len-1] != '/') {
cleaned_path[len] = '/';
cleaned_path[len+1] = '\0';
}
}
} else
sstrncpy(cleaned_path, path, sizeof(cleaned_path));
if (!fs->fs_path)
fs->fs_path = pstrdup(fs->fs_pool, cleaned_path);
/* Check for duplicates. */
if (fs_map->nelts > 0) {
pr_fs_t *fsi = NULL, **fs_objs = (pr_fs_t **) fs_map->elts;
register int i;
for (i = 0; i < fs_map->nelts; i++) {
fsi = fs_objs[i];
if (strcmp(fsi->fs_path, cleaned_path) == 0) {
/* An entry for this path already exists. Make sure the FS being
* mounted is not the same as the one already present.
*/
if (strcmp(fsi->fs_name, fs->fs_name) == 0) {
pr_log_pri(PR_LOG_DEBUG,
"error: duplicate fs paths not allowed: '%s'", cleaned_path);
errno = EEXIST;
return FALSE;
}
/* "Push" the given FS on top of the existing one. */
fs->fs_next = fsi;
fsi->fs_prev = fs;
fs_objs[i] = fs;
chk_fs_map = TRUE;
return TRUE;
}
}
}
/* Push the new FS into the container, then resort the contents. */
*((pr_fs_t **) push_array(fs_map)) = fs;
/* Sort the FSs in the map according to their paths (if there are
* more than one element in the array_header.
*/
if (fs_map->nelts > 1)
qsort(fs_map->elts, fs_map->nelts, sizeof(pr_fs_t *), fs_cmp);
/* Set the flag so that the fs wrapper functions know that a new FS
* has been registered.
*/
chk_fs_map = TRUE;
return TRUE;
}
pr_fs_t *pr_unmount_fs(const char *path, const char *name) {
pr_fs_t *fsi = NULL, **fs_objs = NULL;
register unsigned int i = 0;
/* Sanity check */
if (!path) {
errno = EINVAL;
return NULL;
}
/* This should never be called before pr_register_fs(), but, just in case...*/
if (!fs_map) {
errno = EACCES;
return NULL;
}
fs_objs = (pr_fs_t **) fs_map->elts;
for (i = 0; i < fs_map->nelts; i++) {
fsi = fs_objs[i];
if (strcmp(fsi->fs_path, path) == 0 &&
(name ? strcmp(fsi->fs_name, name) == 0 : TRUE)) {
/* Exact match -- remove this FS. If there is an FS underneath, pop
* the top FS off the stack. Otherwise, allocate a new map. Then
* iterate through the old map, pushing all other FSs into the new map.
* Destroy the old map. Move the new map into place.
*/
if (fsi->fs_next == NULL) {
register unsigned int j = 0;
pr_fs_t *tmp_fs, **old_objs = NULL;
pool *map_pool;
array_header *new_map;
/* If removing this FS would leave an empty map, don't bother
* allocating a new one.
*/
if (fs_map->nelts == 1) {
destroy_pool(fs_map->pool);
fs_map = NULL;
fs_cwd = root_fs;
chk_fs_map = TRUE;
return NULL;
}
map_pool = make_sub_pool(permanent_pool);
new_map = make_array(map_pool, 0, sizeof(pr_fs_t *));
pr_pool_tag(map_pool, "FSIO Map Pool");
old_objs = (pr_fs_t **) fs_map->elts;
for (j = 0; j < fs_map->nelts; j++) {
tmp_fs = old_objs[j];
if (strcmp(tmp_fs->fs_path, path) != 0)
*((pr_fs_t **) push_array(new_map)) = old_objs[j];
}
destroy_pool(fs_map->pool);
fs_map = new_map;
/* Don't forget to set the flag so that wrapper functions scan the
* new map.
*/
chk_fs_map = TRUE;
return fsi;
}
/* "Pop" this FS off the stack. */
if (fsi->fs_next)
fsi->fs_next->fs_prev = NULL;
fs_objs[i] = fsi->fs_next;
fsi->fs_next = fsi->fs_prev = NULL;
chk_fs_map = TRUE;
return fsi;
}
}
return NULL;
}
pr_fs_t *pr_remove_fs(const char *path) {
return pr_unmount_fs(path, NULL);
}
int pr_unregister_fs(const char *path) {
pr_fs_t *fs = NULL;
if (!path) {
errno = EINVAL;
return -1;
}
/* Call pr_remove_fs() to get the fs for this path removed from the
* fs_map.
*/
fs = pr_remove_fs(path);
if (fs) {
destroy_pool(fs->fs_pool);
return 0;
}
errno = ENOENT;
return -1;
}
/* This function returns the best pr_fs_t to handle the given path. It will
* return NULL if there are no registered pr_fs_ts to handle the given path,
* in which case the default root_fs should be used. This is so that
* functions can look to see if an pr_fs_t, other than the default, for a
* given path has been registered, if necessary. If the return value is
* non-NULL, that will be a registered pr_fs_t to handle the given path. In
* this case, if the exact argument is not NULL, it will either be TRUE,
* signifying that the returned pr_fs_t is an exact match for the given
* path, or FALSE, meaning the returned pr_fs_t is a "best match" -- most
* likely the pr_fs_t that handles the directory in which the given path
* occurs.
*/
pr_fs_t *pr_get_fs(const char *path, int *exact) {
pr_fs_t *fs = NULL, **fs_objs = NULL, *best_match_fs = NULL;
register unsigned int i = 0;
/* Sanity check */
if (!path) {
errno = EINVAL;
return NULL;
}
/* Basic optimization -- if there're no elements in the fs_map,
* return the root_fs.
*/
if (!fs_map ||
fs_map->nelts == 0) {
return root_fs;
}
fs_objs = (pr_fs_t **) fs_map->elts;
best_match_fs = root_fs;
/* In order to handle deferred-resolution paths (eg "~" paths), the given
* path will need to be passed through dir_realpath(), if necessary.
*
* The chk_fs_map flag, if TRUE, should be cleared on return of this
* function -- all that flag says is, if TRUE, that this function _might_
* return something different than it did on a previous call.
*/
for (i = 0; i < fs_map->nelts; i++) {
int res = 0;
fs = fs_objs[i];
/* If the current pr_fs_t's path ends in a slash (meaning it is a
* directory, and it matches the first part of the given path,
* assume it to be the best pr_fs_t found so far.
*/
if ((fs->fs_path)[strlen(fs->fs_path) - 1] == '/' &&
!strncmp(path, fs->fs_path, strlen(fs->fs_path)))
best_match_fs = fs;
res = strcmp(fs->fs_path, path);
if (res == 0) {
/* Exact match */
if (exact)
*exact = TRUE;
chk_fs_map = FALSE;
return fs;
} else if (res > 0) {
if (exact)
*exact = FALSE;
chk_fs_map = FALSE;
/* Gone too far - return the best-match pr_fs_t */
return best_match_fs;
}
}
chk_fs_map = FALSE;
/* Return best-match by default */
return best_match_fs;
}
#if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
#ifdef PR_FS_MATCH
void pr_associate_fs(pr_fs_match_t *fsm, pr_fs_t *fs) {
*((pr_fs_t **) push_array(fsm->fsm_fs_objs)) = fs;
}
pr_fs_match_t *pr_create_fs_match(pool *p, const char *name,
const char *pattern, int opmask) {
pr_fs_match_t *fsm = NULL;
pool *match_pool = NULL;
regex_t *regexp = NULL;
int res = 0;
char regerr[80] = {'\0'};
if (!p || !name || !pattern) {
errno = EINVAL;
return NULL;
}
match_pool = make_sub_pool(p);
fsm = (pr_fs_match_t *) pcalloc(match_pool, sizeof(pr_fs_match_t));
if (!fsm)
return NULL;
fsm->fsm_next = NULL;
fsm->fsm_prev = NULL;
fsm->fsm_pool = match_pool;
fsm->fsm_name = pstrdup(fsm->fsm_pool, name);
fsm->fsm_opmask = opmask;
fsm->fsm_pattern = pstrdup(fsm->fsm_pool, pattern);
regexp = pr_regexp_alloc();
if ((res = regcomp(regexp, pattern, REG_EXTENDED|REG_NOSUB)) != 0) {
regerror(res, regexp, regerr, sizeof(regerr));
pr_regexp_free(regexp);
pr_log_pri(PR_LOG_ERR, "unable to compile regex '%s': %s", pattern, regerr);
/* Destroy the just allocated pr_fs_match_t */
destroy_pool(fsm->fsm_pool);
return NULL;
} else
fsm->fsm_regex = regexp;
/* All pr_fs_match_ts start out as null patterns, i.e. no defined callback.
*/
fsm->trigger = NULL;
/* Allocate an array_header, used to record the pointers of any pr_fs_ts
* this pr_fs_match_t may register. This array_header should be accessed
* via associate_fs().
*/
fsm->fsm_fs_objs = make_array(fsm->fsm_pool, 0, sizeof(pr_fs_t *));
return fsm;
}
int pr_insert_fs_match(pr_fs_match_t *fsm) {
pr_fs_match_t *fsmi = NULL;
if (fs_match_list) {
/* Find the end of the fs_match list */
fsmi = fs_match_list;
/* Prevent pr_fs_match_ts with duplicate names */
if (strcmp(fsmi->fsm_name, fsm->fsm_name) == 0) {
pr_log_pri(PR_LOG_DEBUG,
"error: duplicate fs_match names not allowed: '%s'", fsm->fsm_name);
return FALSE;
}
while (fsmi->fsm_next) {
fsmi = fsmi->fsm_next;
if (strcmp(fsmi->fsm_name, fsm->fsm_name) == 0) {
pr_log_pri(PR_LOG_DEBUG,
"error: duplicate fs_match names not allowed: '%s'", fsm->fsm_name);
return FALSE;
}
}
fsm->fsm_next = NULL;
fsm->fsm_prev = fsmi;
fsmi->fsm_next = fsm;
} else
/* This fs_match _becomes_ the start of the fs_match list */
fs_match_list = fsm;
return TRUE;
}
pr_fs_match_t *pr_register_fs_match(pool *p, const char *name,
const char *pattern, int opmask) {
pr_fs_match_t *fsm = NULL;
/* Sanity check */
if (!p || !name || !pattern) {
errno = EINVAL;
return NULL;
}
/* Instantiate an fs_match */
if ((fsm = pr_create_fs_match(p, name, pattern, opmask)) != NULL) {
/* Insert the fs_match into the list */
if (!pr_insert_fs_match(fsm)) {
pr_regexp_free(fsm->fsm_regex);
destroy_pool(fsm->fsm_pool);
return NULL;
}
}
return fsm;
}
int pr_unregister_fs_match(const char *name) {
pr_fs_match_t *fsm = NULL;
pr_fs_t **assoc_fs_objs = NULL, *assoc_fs = NULL;
int removed = FALSE;
/* fs_matches are required to have duplicate names, so using the name as
* the identifier will work.
*/
/* Sanity check*/
if (!name) {
errno = EINVAL;
return FALSE;
}
if (fs_match_list) {
for (fsm = fs_match_list; fsm; fsm = fsm->fsm_next) {
/* Search by name */
if ((name && fsm->fsm_name && strcmp(fsm->fsm_name, name) == 0)) {
/* Remove this fs_match from the list */
if (fsm->fsm_prev)
fsm->fsm_prev->fsm_next = fsm->fsm_next;
if (fsm->fsm_next)
fsm->fsm_next->fsm_prev = fsm->fsm_prev;
/* Check for any pr_fs_ts this pattern may have registered, and
* remove them as well.
*/
assoc_fs_objs = (pr_fs_t **) fsm->fsm_fs_objs->elts;
for (assoc_fs = *assoc_fs_objs; assoc_fs; assoc_fs++)
pr_unregister_fs(assoc_fs->fs_path);
pr_regexp_free(fsm->fsm_regex);
destroy_pool(fsm->fsm_pool);
/* If this fs_match's prev and next pointers are NULL, it is the
* last fs_match in the list. If this is the case, make sure
* that fs_match_list is set to NULL, signalling that there are
* no more registered fs_matches.
*/
if (fsm->fsm_prev == NULL && fsm->fsm_next == NULL) {
fs_match_list = NULL;
fsm = NULL;
}
removed = TRUE;
}
}
}
return (removed ? TRUE : FALSE);
}
pr_fs_match_t *pr_get_next_fs_match(pr_fs_match_t *fsm, const char *path,
int op) {
pr_fs_match_t *fsmi = NULL;
/* Sanity check */
if (!fsm) {
errno = EINVAL;
return NULL;
}
for (fsmi = fsm->fsm_next; fsmi; fsmi = fsmi->fsm_next) {
if ((fsmi->fsm_opmask & op) &&
regexec(fsmi->fsm_regex, path, 0, NULL, 0) == 0)
return fsmi;
}
return NULL;
}
pr_fs_match_t *pr_get_fs_match(const char *path, int op) {
pr_fs_match_t *fsm = NULL;
if (!fs_match_list)
return NULL;
/* Check the first element in the fs_match_list... */
fsm = fs_match_list;
if ((fsm->fsm_opmask & op) &&
regexec(fsm->fsm_regex, path, 0, NULL, 0) == 0)
return fsm;
/* ...otherwise, hand the search off to pr_get_next_fs_match() */
return pr_get_next_fs_match(fsm, path, op);
}
#endif /* PR_FS_MATCH */
#endif /* HAVE_REGEX_H && HAVE_REGCOMP */
void pr_fs_setcwd(const char *dir) {
pr_fs_resolve_path(dir, cwd, sizeof(cwd)-1, FSIO_DIR_CHDIR);
sstrncpy(cwd, dir, sizeof(cwd));
fs_cwd = lookup_dir_fs(cwd, FSIO_DIR_CHDIR);
cwd[sizeof(cwd) - 1] = '\0';
}
const char *pr_fs_getcwd(void) {
return cwd;
}
const char *pr_fs_getvwd(void) {
return vwd;
}
int pr_fs_dircat(char *buf, int buflen, const char *dir1, const char *dir2) {
/* Make temporary copies so that memory areas can overlap */
char *_dir1 = NULL, *_dir2 = NULL;
size_t dir1len = 0;
/* This is a test to see if we've got reasonable directories to concatenate.
*/
if ((strlen(dir1) + strlen(dir2) + 1) >= PR_TUNABLE_PATH_MAX) {
errno = ENAMETOOLONG;
buf[0] = '\0';
return -1;
}
_dir1 = strdup(dir1);
_dir2 = strdup(dir2);
dir1len = strlen(_dir1) - 1;
if (*_dir2 == '/') {
sstrncpy(buf, _dir2, buflen);
free(_dir1);
free(_dir2);
return 0;
}
sstrncpy(buf, _dir1, buflen);
if (buflen && *(_dir1 + dir1len) != '/')
sstrcat(buf, "/", buflen);
sstrcat(buf, _dir2, buflen);
if (!*buf) {
*buf++ = '/';
*buf = '\0';
}
free(_dir1);
free(_dir2);
return 0;
}
/* This function performs any tilde expansion needed and then returns the
* resolved path, if any.
*
* Returns: -1 (errno = ENOENT): user does not exist
* 0 : no interpolation done (path exists)
* 1 : interpolation done
*/
int pr_fs_interpolate(const char *path, char *buf, size_t buflen) {
pool *p = NULL;
struct passwd *pw = NULL;
struct stat sbuf;
char *fname = NULL;
char user[PR_TUNABLE_LOGIN_MAX + 1] = {'\0'};
int len;
if (!path) {
errno = EINVAL;
return -1;
}
if (path[0] == '~') {
fname = strchr(path, '/');
/* Copy over the username.
*/
if (fname) {
len = fname - path;
sstrncpy(user, path + 1, len > sizeof(user) ? sizeof(user) : len);
/* Advance past the '/'. */
fname++;
} else if (pr_fsio_stat(path, &sbuf) == -1) {
/* Otherwise, this might be something like "~foo" which could be a file
* or it could be a user. Let's find out.
*
* Must be a user, if anything...otherwise it's probably a typo.
*/
len = strlen(path);
sstrncpy(user, path + 1, len > sizeof(user) ? sizeof(user) : len);
} else {
/* Otherwise, this _is_ the file in question, perform no interpolation.
*/
fname = (char *) path;
return 0;
}
/* If the user hasn't been explicitly specified, set it here. This
* handles cases such as files beginning with "~", "~/foo" or simply "~".
*/
if (!*user)
sstrncpy(user, session.user, sizeof(user));
/* The permanent pool is used here, rather than session.pool, as path
* interpolation can occur during startup parsing, when session.pool does
* not exist. It does not really matter, since the allocated sub pool
* is destroyed shortly.
*/
p = make_sub_pool(permanent_pool);
pr_pool_tag(p, "pr_fs_interpolate() pool");
pw = pr_auth_getpwnam(p, user);
if (!pw) {
destroy_pool(p);
errno = ENOENT;
return -1;
}
sstrncpy(buf, pw->pw_dir, buflen);
/* Done with pw, which means we can destroy the temporary pool now. */
destroy_pool(p);
len = strlen(buf);
if (fname && len < buflen && buf[len - 1] != '/')
buf[len++] = '/';
if (fname)
sstrncpy(&buf[len], fname, buflen - len);
} else
sstrncpy(buf, path, buflen);
return 1;
}
int pr_fs_resolve_partial(const char *path, char *buf, size_t buflen, int op) {
char curpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
namebuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
*where = NULL, *ptr = NULL, *last = NULL;
pr_fs_t *fs = NULL;
int len = 0, fini = 1, link_cnt = 0;
ino_t last_inode = 0;
dev_t last_device = 0;
struct stat sbuf;
if (!path) {
errno = EINVAL;
return -1;
}
if (*path != '/') {
if (*path == '~') {
switch (pr_fs_interpolate(path, curpath, sizeof(curpath)-1)) {
case -1:
return -1;
case 0:
sstrncpy(curpath, path, sizeof(curpath));
sstrncpy(workpath, cwd, sizeof(workpath));
break;
}
} else {
sstrncpy(curpath, path, sizeof(curpath));
sstrncpy(workpath, cwd, sizeof(workpath));
}
} else
sstrncpy(curpath, path, sizeof(curpath));
while (fini--) {
where = curpath;
while (*where != '\0') {
/* Handle "." */
if (strcmp(where, ".") == 0) {
where++;
continue;
}
/* Handle ".." */
if (strcmp(where, "..") == 0) {
where += 2;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
/* Handle "./" */
if (!strncmp(where, "./", 2)) {
where += 2;
continue;
}
/* Handle "../" */
if (!strncmp(where, "../", 3)) {
where += 3;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
ptr = strchr(where, '/');
if (!ptr)
ptr = where + strlen(where) - 1;
else
*ptr = '\0';
sstrncpy(namebuf, workpath, sizeof(namebuf));
if (*namebuf) {
for (last = namebuf; *last; last++);
if (*--last != '/')
sstrcat(namebuf, "/", sizeof(namebuf)-1);
} else
sstrcat(namebuf, "/", sizeof(namebuf)-1);
sstrcat(namebuf, where, sizeof(namebuf)-1);
where = ++ptr;
fs = lookup_dir_fs(namebuf, op);
if (fs_cache_lstat(fs, namebuf, &sbuf) == -1)
return -1;
if (S_ISLNK(sbuf.st_mode)) {
char linkpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
/* Detect an obvious recursive symlink */
if (sbuf.st_ino && (ino_t) sbuf.st_ino == last_inode &&
sbuf.st_dev && (dev_t) sbuf.st_dev == last_device) {
errno = ELOOP;
return -1;
}
last_inode = (ino_t) sbuf.st_ino;
last_device = (dev_t) sbuf.st_dev;
if (++link_cnt > 32) {
errno = ELOOP;
return -1;
}
len = pr_fsio_readlink(namebuf, linkpath, sizeof(linkpath)-1);
if (len <= 0) {
errno = ENOENT;
return -1;
}
*(linkpath + len) = '\0';
if (*linkpath == '/')
*workpath = '\0';
if (*linkpath == '~') {
char tmpbuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
*workpath = '\0';
sstrncpy(tmpbuf, linkpath, sizeof(tmpbuf));
if (pr_fs_interpolate(tmpbuf, linkpath, sizeof(linkpath)-1) == -1)
return -1;
}
if (*where) {
sstrcat(linkpath, "/", sizeof(linkpath)-1);
sstrcat(linkpath, where, sizeof(linkpath)-1);
}
sstrncpy(curpath, linkpath, sizeof(curpath));
fini++;
break; /* continue main loop */
}
if (S_ISDIR(sbuf.st_mode)) {
sstrncpy(workpath, namebuf, sizeof(workpath));
continue;
}
if (*where) {
errno = ENOENT;
return -1; /* path/notadir/morepath */
} else {
sstrncpy(workpath, namebuf, sizeof(workpath));
}
}
}
if (!workpath[0])
sstrncpy(workpath, "/", sizeof(workpath));
sstrncpy(buf, workpath, buflen);
return 0;
}
int pr_fs_resolve_path(const char *path, char *buf, size_t buflen, int op) {
char curpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
namebuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
*where = NULL, *ptr = NULL, *last = NULL;
pr_fs_t *fs = NULL;
int len = 0, fini = 1, link_cnt = 0;
ino_t last_inode = 0;
dev_t last_device = 0;
struct stat sbuf;
if (!path) {
errno = EINVAL;
return -1;
}
if (pr_fs_interpolate(path, curpath, sizeof(curpath)-1) != -1)
sstrncpy(curpath, path, sizeof(curpath));
if (curpath[0] != '/')
sstrncpy(workpath, cwd, sizeof(workpath));
else
workpath[0] = '\0';
while (fini--) {
where = curpath;
while (*where != '\0') {
if (strcmp(where, ".") == 0) {
where++;
continue;
}
/* handle "./" */
if (!strncmp(where, "./", 2)) {
where += 2;
continue;
}
/* handle "../" */
if (!strncmp(where, "../", 3)) {
where += 3;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
ptr = strchr(where, '/');
if (!ptr)
ptr = where + strlen(where) - 1;
else
*ptr = '\0';
sstrncpy(namebuf, workpath, sizeof(namebuf));
if (*namebuf) {
for (last = namebuf; *last; last++);
if (*--last != '/')
sstrcat(namebuf, "/", sizeof(namebuf)-1);
} else
sstrcat(namebuf, "/", sizeof(namebuf)-1);
sstrcat(namebuf, where, sizeof(namebuf)-1);
where = ++ptr;
fs = lookup_dir_fs(namebuf, op);
if (fs_cache_lstat(fs, namebuf, &sbuf) == -1) {
errno = ENOENT;
return -1;
}
if (S_ISLNK(sbuf.st_mode)) {
char linkpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
/* Detect an obvious recursive symlink */
if (sbuf.st_ino && (ino_t) sbuf.st_ino == last_inode &&
sbuf.st_dev && (dev_t) sbuf.st_dev == last_device) {
errno = ELOOP;
return -1;
}
last_inode = (ino_t) sbuf.st_ino;
last_device = (dev_t) sbuf.st_dev;
if (++link_cnt > 32) {
errno = ELOOP;
return -1;
}
len = pr_fsio_readlink(namebuf, linkpath, sizeof(linkpath)-1);
if (len <= 0) {
errno = ENOENT;
return -1;
}
*(linkpath+len) = '\0';
if (*linkpath == '/')
*workpath = '\0';
if (*linkpath == '~') {
char tmpbuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
*workpath = '\0';
sstrncpy(tmpbuf, linkpath, sizeof(tmpbuf));
if (pr_fs_interpolate(tmpbuf, linkpath, sizeof(linkpath)-1) == -1)
return -1;
}
if (*where) {
sstrcat(linkpath, "/", sizeof(linkpath)-1);
sstrcat(linkpath, where, sizeof(linkpath)-1);
}
sstrncpy(curpath, linkpath, sizeof(curpath));
fini++;
break; /* continue main loop */
}
if (S_ISDIR(sbuf.st_mode)) {
sstrncpy(workpath, namebuf, sizeof(workpath));
continue;
}
if (*where) {
errno = ENOENT;
return -1; /* path/notadir/morepath */
} else
sstrncpy(workpath, namebuf, sizeof(workpath));
}
}
if (!workpath[0])
sstrncpy(workpath, "/", sizeof(workpath));
sstrncpy(buf, workpath, buflen);
return 0;
}
void pr_fs_clean_path(const char *path, char *buf, size_t buflen) {
char workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
char curpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
char namebuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
char *where = NULL, *ptr = NULL, *last = NULL;
int fini = 1;
if (!path)
return;
sstrncpy(curpath, path, sizeof(curpath));
/* main loop */
while (fini--) {
where = curpath;
while (*where != '\0') {
if (strcmp(where, ".") == 0) {
where++;
continue;
}
/* handle "./" */
if (!strncmp(where, "./", 2)) {
where += 2;
continue;
}
/* handle ".." */
if (strcmp(where, "..") == 0) {
where += 2;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
/* handle "../" */
if (!strncmp(where, "../", 3)) {
where += 3;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
ptr = strchr(where, '/');
if (!ptr)
ptr = where + strlen(where) - 1;
else
*ptr = '\0';
sstrncpy(namebuf, workpath, sizeof(namebuf));
if (*namebuf) {
for (last = namebuf; *last; last++);
if (*--last != '/')
sstrcat(namebuf, "/", sizeof(namebuf)-1);
} else
sstrcat(namebuf, "/", sizeof(namebuf)-1);
sstrcat(namebuf, where, sizeof(namebuf)-1);
namebuf[sizeof(namebuf)-1] = '\0';
where = ++ptr;
sstrncpy(workpath, namebuf, sizeof(workpath));
}
}
if (!workpath[0])
sstrncpy(workpath, "/", sizeof(workpath));
sstrncpy(buf, workpath, buflen);
}
/* This function checks the given path's prefix against the paths that
* have been registered. If no matching path prefix has been registered,
* the path is considered invalid.
*/
int pr_fs_valid_path(const char *path) {
if (fs_map && fs_map->nelts > 0) {
pr_fs_t *fsi = NULL, **fs_objs = (pr_fs_t **) fs_map->elts;
register int i;
for (i = 0; i < fs_map->nelts; i++) {
fsi = fs_objs[i];
if (strncmp(fsi->fs_path, path, strlen(fsi->fs_path)) == 0)
return 0;
}
}
/* Also check the path against the default '/' path. */
if (*path == '/')
return 0;
errno = EINVAL;
return -1;
}
void pr_fs_virtual_path(const char *path, char *buf, size_t buflen) {
char curpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
namebuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'},
*where = NULL, *ptr = NULL, *last = NULL;
int fini = 1;
if (!path)
return;
if (pr_fs_interpolate(path, curpath, sizeof(curpath)-1) != -1)
sstrncpy(curpath, path, sizeof(curpath));
if (curpath[0] != '/')
sstrncpy(workpath, vwd, sizeof(workpath));
else
workpath[0] = '\0';
/* curpath is path resolving */
/* linkpath is path a symlink pointed to */
/* workpath is the path we've resolved */
/* main loop */
while (fini--) {
where = curpath;
while (*where != '\0') {
if (strcmp(where, ".") == 0) {
where++;
continue;
}
/* handle "./" */
if (!strncmp(where, "./", 2)) {
where += 2;
continue;
}
/* handle ".." */
if (strcmp(where, "..") == 0) {
where += 2;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
/* handle "../" */
if (!strncmp(where, "../", 3)) {
where += 3;
ptr = last = workpath;
while (*ptr) {
if (*ptr == '/')
last = ptr;
ptr++;
}
*last = '\0';
continue;
}
ptr = strchr(where, '/');
if (!ptr)
ptr = where + strlen(where) - 1;
else
*ptr = '\0';
sstrncpy(namebuf, workpath, sizeof(namebuf));
if (*namebuf) {
for (last = namebuf; *last; last++);
if (*--last != '/')
sstrcat(namebuf, "/", sizeof(namebuf)-1);
} else
sstrcat(namebuf, "/", sizeof(namebuf)-1);
sstrcat(namebuf, where, sizeof(namebuf)-1);
where = ++ptr;
sstrncpy(workpath, namebuf, sizeof(workpath));
}
}
if (!workpath[0])
sstrncpy(workpath, "/", sizeof(workpath));
sstrncpy(buf, workpath, buflen);
}
int pr_fsio_chdir_canon(const char *path, int hidesymlink) {
char resbuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
pr_fs_t *fs = NULL;
int res = 0;
if (pr_fs_resolve_partial(path, resbuf, sizeof(resbuf)-1,
FSIO_DIR_CHDIR) == -1)
return -1;
fs = lookup_dir_fs(resbuf, FSIO_DIR_CHDIR);
/* Find the first non-NULL custom chdir handler. If there are none,
* use the system chdir.
*/
while (fs && fs->fs_next && !fs->chdir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chdir()", fs->fs_name);
res = fs->chdir(fs, resbuf);
if (res != -1) {
/* chdir succeeded, so we set fs_cwd for future references. */
fs_cwd = fs ? fs : root_fs;
if (hidesymlink)
pr_fs_virtual_path(path, vwd, sizeof(vwd)-1);
else
sstrncpy(vwd, resbuf, sizeof(vwd));
}
return res;
}
int pr_fsio_chdir(const char *path, int hidesymlink) {
char resbuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
pr_fs_t *fs = NULL;
int res;
pr_fs_clean_path(path, resbuf, sizeof(resbuf)-1);
fs = lookup_dir_fs(path, FSIO_DIR_CHDIR);
/* Find the first non-NULL custom chdir handler. If there are none,
* use the system chdir.
*/
while (fs && fs->fs_next && !fs->chdir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chdir()", fs->fs_name);
res = fs->chdir(fs, resbuf);
if (res != -1) {
/* chdir succeeded, so we set fs_cwd for future references. */
fs_cwd = fs;
if (hidesymlink)
pr_fs_virtual_path(path, vwd, sizeof(vwd)-1);
else
sstrncpy(vwd, resbuf, sizeof(vwd));
}
return res;
}
/* fs_opendir, fs_closedir and fs_readdir all use a nifty
* optimization, caching the last-recently-used pr_fs_t, and
* avoid future pr_fs_t lookups when iterating via readdir.
*/
void *pr_fsio_opendir(const char *path) {
pr_fs_t *fs = NULL;
fsopendir_t *fsod = NULL, *fsodi = NULL;
pool *fsod_pool = NULL;
DIR *res = NULL;
if (strchr(path, '/') == NULL) {
fs = fs_cwd;
} else {
char buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
if (pr_fs_resolve_partial(path, buf, sizeof(buf)-1, FSIO_DIR_OPENDIR) == -1)
return NULL;
fs = lookup_dir_fs(buf, FSIO_DIR_OPENDIR);
}
/* Find the first non-NULL custom opendir handler. If there are none,
* use the system opendir.
*/
while (fs && fs->fs_next && !fs->opendir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s opendir()", fs->fs_name);
res = fs->opendir(fs, path);
if (!res)
return NULL;
/* Cache it here */
fs_cache_dir = res;
fs_cache_fsdir = fs;
fsod_pool = make_sub_pool(permanent_pool);
pr_pool_tag(fsod_pool, "fsod subpool");
fsod = pcalloc(fsod_pool, sizeof(fsopendir_t));
if (!fsod) {
if (fs->closedir) {
fs->closedir(fs, res);
errno = ENOMEM;
return NULL;
} else {
sys_closedir(fs, res);
errno = ENOMEM;
return NULL;
}
}
fsod->pool = fsod_pool;
fsod->dir = res;
fsod->fsdir = fs;
fsod->next = NULL;
fsod->prev = NULL;
if (fsopendir_list) {
/* find the end of the fsopendir list */
fsodi = fsopendir_list;
while (fsodi->next)
fsodi = fsodi->next;
fsod->next = NULL;
fsod->prev = fsodi;
fsodi->next = fsod;
} else
/* This fsopendir _becomes_ the start of the fsopendir list */
fsopendir_list = fsod;
return res;
}
static pr_fs_t *find_opendir(void *dir, int closing) {
pr_fs_t *fs = NULL;
if (fsopendir_list) {
fsopendir_t *fsod;
for (fsod = fsopendir_list; fsod; fsod = fsod->next) {
if (fsod->dir && fsod->dir == dir) {
fs = fsod->fsdir;
break;
}
}
if (closing && fsod) {
if (fsod->prev)
fsod->prev->next = fsod->next;
if (fsod->next)
fsod->next->prev = fsod->prev;
if (fsod == fsopendir_list)
fsopendir_list = fsod->next;
destroy_pool(fsod->pool);
}
}
if (dir == fs_cache_dir) {
fs = fs_cache_fsdir;
if (closing) {
fs_cache_dir = NULL;
fs_cache_fsdir = NULL;
}
}
return fs;
}
int pr_fsio_closedir(void *dir) {
int res;
pr_fs_t *fs = find_opendir(dir, TRUE);
if (!fs)
return -1;
/* Find the first non-NULL custom closedir handler. If there are none,
* use the system closedir.
*/
while (fs && fs->fs_next && !fs->closedir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s closedir()", fs->fs_name);
res = fs->closedir(fs, dir);
return res;
}
struct dirent *pr_fsio_readdir(void *dir) {
struct dirent *res;
pr_fs_t *fs = find_opendir(dir, FALSE);
if (!fs)
return NULL;
/* Find the first non-NULL custom readdir handler. If there are none,
* use the system readdir.
*/
while (fs && fs->fs_next && !fs->readdir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s readdir()", fs->fs_name);
res = fs->readdir(fs, dir);
return res;
}
int pr_fsio_mkdir(const char *path, mode_t mode) {
int res;
pr_fs_t *fs = lookup_dir_fs(path, FSIO_DIR_MKDIR);
/* Find the first non-NULL custom mkdir handler. If there are none,
* use the system mkdir.
*/
while (fs && fs->fs_next && !fs->mkdir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s mkdir()", fs->fs_name);
res = fs->mkdir(fs, path, mode);
return res;
}
int pr_fsio_rmdir(const char *path) {
int res;
pr_fs_t *fs = lookup_dir_fs(path, FSIO_DIR_RMDIR);
/* Find the first non-NULL custom rmdir handler. If there are none,
* use the system rmdir.
*/
while (fs && fs->fs_next && !fs->rmdir)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s rmdir()", fs->fs_name);
res = fs->rmdir(fs, path);
return res;
}
int pr_fsio_stat_canon(const char *path, struct stat *sbuf) {
pr_fs_t *fs = lookup_file_canon_fs(path, NULL, FSIO_FILE_STAT);
/* Find the first non-NULL custom stat handler. If there are none,
* use the system stat.
*/
while (fs && fs->fs_next && !fs->stat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s stat()", fs ? fs->fs_name : "system");
return fs_cache_stat(fs ? fs : root_fs, path, sbuf);
}
int pr_fsio_stat(const char *path, struct stat *sbuf) {
pr_fs_t *fs = lookup_file_fs(path, NULL, FSIO_FILE_STAT);
/* Find the first non-NULL custom stat handler. If there are none,
* use the system stat.
*/
while (fs && fs->fs_next && !fs->stat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s stat()", fs->fs_name);
return fs_cache_stat(fs ? fs : root_fs, path, sbuf);
}
int pr_fsio_fstat(pr_fh_t *fh, struct stat *sbuf) {
int res;
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom fstat handler. If there are none,
* use the system fstat.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->fstat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s fstat()", fs->fs_name);
res = fs->fstat(fh, fh->fh_fd, sbuf);
return res;
}
int pr_fsio_lstat_canon(const char *path, struct stat *sbuf) {
pr_fs_t *fs = lookup_file_canon_fs(path, NULL, FSIO_FILE_LSTAT);
/* Find the first non-NULL custom lstat handler. If there are none,
* use the system lstat.
*/
while (fs && fs->fs_next && !fs->lstat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s lstat()", fs ? fs->fs_name : "system");
return fs_cache_lstat(fs ? fs : root_fs, path, sbuf);
}
int pr_fsio_lstat(const char *path, struct stat *sbuf) {
pr_fs_t *fs = lookup_file_fs(path, NULL, FSIO_FILE_LSTAT);
/* Find the first non-NULL custom lstat handler. If there are none,
* use the system lstat.
*/
while (fs && fs->fs_next && !fs->lstat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s lstat()", fs->fs_name);
return fs_cache_lstat(fs ? fs : root_fs, path, sbuf);
}
int pr_fsio_readlink_canon(const char *path, char *buf, size_t buflen) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(path, NULL, FSIO_FILE_READLINK);
/* Find the first non-NULL custom readlink handler. If there are none,
* use the system readlink.
*/
while (fs && fs->fs_next && !fs->readlink)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s readlink()", fs->fs_name);
res = fs->readlink(fs, path, buf, buflen);
return res;
}
int pr_fsio_readlink(const char *path, char *buf, size_t buflen) {
int res;
pr_fs_t *fs = lookup_file_fs(path, NULL, FSIO_FILE_READLINK);
/* Find the first non-NULL custom readlink handler. If there are none,
* use the system readlink.
*/
while (fs && fs->fs_next && !fs->readlink)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s readlink()", fs->fs_name);
res = fs->readlink(fs, path, buf, buflen);
return res;
}
/* pr_fs_glob() is just a wrapper for glob(3), setting the various gl_
* callbacks to our fs functions.
*/
int pr_fs_glob(const char *pattern, int flags,
int (*errfunc)(const char *, int), glob_t *pglob) {
if (pglob) {
flags |= GLOB_ALTDIRFUNC;
pglob->gl_closedir = (void (*)(void *)) pr_fsio_closedir;
pglob->gl_readdir = pr_fsio_readdir;
pglob->gl_opendir = pr_fsio_opendir;
pglob->gl_lstat = pr_fsio_lstat;
pglob->gl_stat = pr_fsio_stat;
}
return glob(pattern, flags, errfunc, pglob);
}
void pr_fs_globfree(glob_t *pglob) {
globfree(pglob);
}
int pr_fsio_rename_canon(const char *rfrom, const char *rto) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(rfrom, NULL, FSIO_FILE_RENAME);
if (fs != lookup_file_canon_fs(rto, NULL, FSIO_FILE_RENAME)) {
errno = EXDEV;
return -1;
}
/* Find the first non-NULL custom rename handler. If there are none,
* use the system rename.
*/
while (fs && fs->fs_next && !fs->rename)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s rename()", fs->fs_name);
res = fs->rename(fs, rfrom, rto);
return res;
}
int pr_fsio_rename(const char *rnfm, const char *rnto) {
int res;
pr_fs_t *fs = lookup_file_fs(rnfm, NULL, FSIO_FILE_RENAME);
if (fs != lookup_file_fs(rnto, NULL, FSIO_FILE_RENAME)) {
errno = EXDEV;
return -1;
}
/* Find the first non-NULL custom rename handler. If there are none,
* use the system rename.
*/
while (fs && fs->fs_next && !fs->rename)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s rename()", fs->fs_name);
res = fs->rename(fs, rnfm, rnto);
return res;
}
int pr_fsio_unlink_canon(const char *name) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(name, NULL, FSIO_FILE_UNLINK);
/* Find the first non-NULL custom unlink handler. If there are none,
* use the system unlink.
*/
while (fs && fs->fs_next && !fs->unlink)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s unlink()", fs->fs_name);
res = fs->unlink(fs, name);
return res;
}
int pr_fsio_unlink(const char *name) {
int res;
pr_fs_t *fs = lookup_file_fs(name, NULL, FSIO_FILE_UNLINK);
/* Find the first non-NULL custom unlink handler. If there are none,
* use the system unlink.
*/
while (fs && fs->fs_next && !fs->unlink)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s unlink()", fs->fs_name);
res = fs->unlink(fs, name);
return res;
}
pr_fh_t *pr_fsio_open_canon(const char *name, int flags) {
char *deref = NULL;
pool *tmp_pool = NULL;
pr_fh_t *fh = NULL;
pr_fs_t *fs = lookup_file_canon_fs(name, &deref, FSIO_FILE_OPEN);
/* Allocate a filehandle. */
tmp_pool = make_sub_pool(fs->fs_pool);
pr_pool_tag(tmp_pool, "pr_fsio_open_canon() subpool");
fh = pcalloc(tmp_pool, sizeof(pr_fh_t));
fh->fh_pool = tmp_pool;
fh->fh_path = pstrdup(fh->fh_pool, name);
fh->fh_fd = -1;
fh->fh_buf = NULL;
fh->fh_fs = fs;
/* Find the first non-NULL custom open handler. If there are none,
* use the system open.
*/
while (fs && fs->fs_next && !fs->open)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s open()", fs->fs_name);
fh->fh_fd = fs->open(fh, deref, flags);
if (fh->fh_fd == -1) {
destroy_pool(fh->fh_pool);
return NULL;
}
return fh;
}
pr_fh_t *pr_fsio_open(const char *name, int flags) {
pool *tmp_pool = NULL;
pr_fh_t *fh = NULL;
pr_fs_t *fs = NULL;
if (!name) {
errno = EINVAL;
return NULL;
}
fs = lookup_file_fs(name, NULL, FSIO_FILE_OPEN);
/* Allocate a filehandle. */
tmp_pool = make_sub_pool(fs->fs_pool);
pr_pool_tag(tmp_pool, "pr_fsio_open() subpool");
fh = pcalloc(tmp_pool, sizeof(pr_fh_t));
fh->fh_pool = tmp_pool;
fh->fh_path = pstrdup(fh->fh_pool, name);
fh->fh_fd = -1;
fh->fh_buf = NULL;
fh->fh_fs = fs;
/* Find the first non-NULL custom open handler. If there are none,
* use the system open.
*/
while (fs && fs->fs_next && !fs->open)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s open()", fs->fs_name);
fh->fh_fd = fs->open(fh, name, flags);
if (fh->fh_fd == -1) {
destroy_pool(fh->fh_pool);
return NULL;
}
return fh;
}
pr_fh_t *pr_fsio_creat_canon(const char *name, mode_t mode) {
char *deref = NULL;
pool *tmp_pool = NULL;
pr_fh_t *fh = NULL;
pr_fs_t *fs = lookup_file_canon_fs(name, &deref, FSIO_FILE_CREAT);
/* Allocate a filehandle. */
tmp_pool = make_sub_pool(fs->fs_pool);
pr_pool_tag(tmp_pool, "pr_fsio_creat_canon() subpool");
fh = pcalloc(tmp_pool, sizeof(pr_fh_t));
fh->fh_pool = tmp_pool;
fh->fh_path = pstrdup(fh->fh_pool, name);
fh->fh_fd = -1;
fh->fh_buf = NULL;
fh->fh_fs = fs;
/* Find the first non-NULL custom creat handler. If there are none,
* use the system creat.
*/
while (fs && fs->fs_next && !fs->creat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s creat()", fs->fs_name);
fh->fh_fd = fs->creat(fh, deref, mode);
if (fh->fh_fd == -1) {
destroy_pool(fh->fh_pool);
return NULL;
}
return fh;
}
pr_fh_t *pr_fsio_creat(const char *name, mode_t mode) {
pool *tmp_pool = NULL;
pr_fh_t *fh = NULL;
pr_fs_t *fs = lookup_file_fs(name, NULL, FSIO_FILE_CREAT);
/* Allocate a filehandle. */
tmp_pool = make_sub_pool(fs->fs_pool);
pr_pool_tag(tmp_pool, "pr_fsio_creat() subpool");
fh = pcalloc(tmp_pool, sizeof(pr_fh_t));
fh->fh_pool = tmp_pool;
fh->fh_path = pstrdup(fh->fh_pool, name);
fh->fh_fd = -1;
fh->fh_buf = NULL;
fh->fh_fs = fs;
/* Find the first non-NULL custom creat handler. If there are none,
* use the system creat.
*/
while (fs && fs->fs_next && !fs->creat)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s creat()", fs->fs_name);
fh->fh_fd = fs->creat(fh, name, mode);
if (fh->fh_fd == -1) {
destroy_pool(fh->fh_pool);
return NULL;
}
return fh;
}
int pr_fsio_close(pr_fh_t *fh) {
int res = 0;
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom close handler. If there are none,
* use the system close.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->close)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s close()", fs->fs_name);
res = fs->close(fh, fh->fh_fd);
destroy_pool(fh->fh_pool);
return res;
}
int pr_fsio_read(pr_fh_t *fh, char *buf, size_t size) {
int res;
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom read handler. If there are none,
* use the system read.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->read)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s read()", fs->fs_name);
res = fs->read(fh, fh->fh_fd, buf, size);
return res;
}
int pr_fsio_write(pr_fh_t *fh, const char *buf, size_t size) {
int res;
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom write handler. If there are none,
* use the system write.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->write)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s write()", fs->fs_name);
res = fs->write(fh, fh->fh_fd, buf, size);
return res;
}
off_t pr_fsio_lseek(pr_fh_t *fh, off_t offset, int whence) {
off_t res;
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom lseek handler. If there are none,
* use the system lseek.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->lseek)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s lseek()", fs->fs_name);
res = fs->lseek(fh, fh->fh_fd, offset, whence);
return res;
}
int pr_fsio_link_canon(const char *lfrom, const char *lto) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(lfrom, NULL, FSIO_FILE_LINK);
if (fs != lookup_file_canon_fs(lto, NULL, FSIO_FILE_LINK)) {
errno = EXDEV;
return -1;
}
/* Find the first non-NULL custom link handler. If there are none,
* use the system link.
*/
while (fs && fs->fs_next && !fs->link)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s link()", fs->fs_name);
res = fs->link(fs, lfrom, lto);
return res;
}
int pr_fsio_link(const char *lfrom, const char *lto) {
int res;
pr_fs_t *fs = lookup_file_fs(lfrom, NULL, FSIO_FILE_LINK);
if (fs != lookup_file_fs(lto, NULL, FSIO_FILE_LINK)) {
errno = EXDEV;
return -1;
}
/* Find the first non-NULL custom link handler. If there are none,
* use the system link.
*/
while (fs && fs->fs_next && !fs->link)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s link()", fs->fs_name);
res = fs->link(fs, lfrom, lto);
return res;
}
int pr_fsio_symlink_canon(const char *lfrom, const char *lto) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(lto, NULL, FSIO_FILE_SYMLINK);
/* Find the first non-NULL custom symlink handler. If there are none,
* use the system symlink
*/
while (fs && fs->fs_next && !fs->symlink)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s symlink()", fs->fs_name);
res = fs->symlink(fs, lfrom, lto);
return res;
}
int pr_fsio_symlink(const char *lfrom, const char *lto) {
int res;
pr_fs_t *fs = lookup_file_fs(lto, NULL, FSIO_FILE_SYMLINK);
/* Find the first non-NULL custom symlink handler. If there are none,
* use the system symlink.
*/
while (fs && fs->fs_next && !fs->symlink)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s symlink()", fs->fs_name);
res = fs->symlink(fs, lfrom, lto);
return res;
}
int pr_fsio_ftruncate(pr_fh_t *fh, off_t len) {
int res;
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom ftruncate handler. If there are none,
* use the system ftruncate.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->ftruncate)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s ftruncate()", fs->fs_name);
res = fs->ftruncate(fh, fh->fh_fd, len);
return res;
}
int pr_fsio_truncate_canon(const char *path, off_t len) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(path, NULL, FSIO_FILE_TRUNC);
/* Find the first non-NULL custom truncate handler. If there are none,
* use the system truncate.
*/
while (fs && fs->fs_next && !fs->truncate)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s truncate()", fs->fs_name);
res = fs->truncate(fs, path, len);
return res;
}
int pr_fsio_truncate(const char *path, off_t len) {
int res;
pr_fs_t *fs = lookup_file_fs(path, NULL, FSIO_FILE_TRUNC);
/* Find the first non-NULL custom truncate handler. If there are none,
* use the system truncate.
*/
while (fs && fs->fs_next && !fs->truncate)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s truncate()", fs->fs_name);
res = fs->truncate(fs, path, len);
return res;
}
int pr_fsio_chmod_canon(const char *name, mode_t mode) {
int res;
char *deref = NULL;
pr_fs_t *fs = lookup_file_canon_fs(name, &deref, FSIO_FILE_CHMOD);
/* Find the first non-NULL custom chmod handler. If there are none,
* use the system chmod.
*/
while (fs && fs->fs_next && !fs->chmod)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chmod()", fs->fs_name);
res = fs->chmod(fs, deref, mode);
return res;
}
int pr_fsio_chmod(const char *name, mode_t mode) {
int res;
pr_fs_t *fs = lookup_file_fs(name, NULL, FSIO_FILE_CHMOD);
/* Find the first non-NULL custom chmod handler. If there are none,
* use the system chmod.
*/
while (fs && fs->fs_next && !fs->chmod)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chmod()", fs->fs_name);
res = fs->chmod(fs, name, mode);
return res;
}
int pr_fsio_chown_canon(const char *name, uid_t uid, gid_t gid) {
int res;
pr_fs_t *fs = lookup_file_canon_fs(name, NULL, FSIO_FILE_CHOWN);
/* Find the first non-NULL custom chown handler. If there are none,
* use the system chown.
*/
while (fs && fs->fs_next && !fs->chown)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chown()", fs->fs_name);
res = fs->chown(fs, name, uid, gid);
return res;
}
int pr_fsio_chown(const char *name, uid_t uid, gid_t gid) {
int res;
pr_fs_t *fs = lookup_file_fs(name, NULL, FSIO_FILE_CHOWN);
/* Find the first non-NULL custom chown handler. If there are none,
* use the system chown.
*/
while (fs && fs->fs_next && !fs->chown)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chown()", fs->fs_name);
res = fs->chown(fs, name, uid, gid);
return res;
}
int pr_fsio_access(const char *path, int mode, uid_t uid, gid_t gid,
array_header *suppl_gids) {
pr_fs_t *fs;
if (!path) {
errno = EINVAL;
return -1;
}
fs = lookup_file_fs(path, NULL, FSIO_FILE_ACCESS);
/* Find the first non-NULL custom access handler. If there are none,
* use the system access.
*/
while (fs && fs->fs_next && !fs->access)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s access()", fs->fs_name);
return fs->access(fs, path, mode, uid, gid, suppl_gids);
}
int pr_fsio_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid,
array_header *suppl_gids) {
pr_fs_t *fs;
if (!fh) {
errno = EINVAL;
return -1;
}
/* Find the first non-NULL custom faccess handler. If there are none,
* use the system faccess.
*/
fs = fh->fh_fs;
while (fs && fs->fs_next && !fs->faccess)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s faccess()", fs->fs_name);
return fh->fh_fs->faccess(fh, mode, uid, gid, suppl_gids);
}
/* If the wrapped chroot() function suceeds (eg returns 0), then all
* pr_fs_ts currently registered in the fs_map will have their paths
* rewritten to reflect the new root.
*/
int pr_fsio_chroot(const char *path) {
int res = 0;
pr_fs_t *fs = lookup_dir_fs(path, FSIO_DIR_CHROOT);
/* Find the first non-NULL custom chroot handler. If there are none,
* use the system chroot.
*/
while (fs && fs->fs_next && !fs->chroot)
fs = fs->fs_next;
pr_log_debug(DEBUG9, "FS: using %s chroot()", fs->fs_name);
res = fs->chroot(fs, path);
if (res == 0) {
unsigned int iter_start = 0;
/* The filesystem handles in fs_map need to be readjusted to the new root.
*/
register unsigned int i = 0;
pool *map_pool = make_sub_pool(permanent_pool);
array_header *new_map = make_array(map_pool, 0, sizeof(pr_fs_t *));
pr_fs_t **fs_objs = NULL;
pr_pool_tag(map_pool, "FSIO Map Pool");
if (fs_map)
fs_objs = (pr_fs_t **) fs_map->elts;
if (fs != root_fs) {
if (strncmp(fs->fs_path, path, strlen(path)) == 0) {
memmove(fs->fs_path, fs->fs_path + strlen(path),
strlen(fs->fs_path) - strlen(path) + 1);
}
*((pr_fs_t **) push_array(new_map)) = fs;
iter_start = 1;
}
for (i = iter_start; i < (fs_map ? fs_map->nelts : 0); i++) {
pr_fs_t *tmpfs = fs_objs[i];
/* The memory for this field has already been allocated, so futzing
* with it like this should be fine. Watch out for any paths that
* may be different, e.g. added manually, not through pr_register_fs().
* Any absolute paths that are outside of the chroot path are discarded.
* Deferred-resolution paths (eg "~" paths) and relative paths are kept.
*/
if (strncmp(tmpfs->fs_path, path, strlen(path)) == 0) {
pr_fs_t *next;
memmove(tmpfs->fs_path, tmpfs->fs_path + strlen(path),
strlen(tmpfs->fs_path) - strlen(path) + 1);
/* Need to do this for any stacked FSs as well. */
next = tmpfs->fs_next;
while (next) {
memmove(next->fs_path, next->fs_path + strlen(path),
strlen(next->fs_path) - strlen(path) + 1);
next = next->fs_next;
}
}
/* Add this FS to the new fs_map. */
*((pr_fs_t **) push_array(new_map)) = tmpfs;
}
/* Sort the new map */
qsort(new_map->elts, new_map->nelts, sizeof(pr_fs_t *), fs_cmp);
/* Destroy the old map */
if (fs_map)
destroy_pool(fs_map->pool);
fs_map = new_map;
chk_fs_map = TRUE;
}
return res;
}
char *pr_fsio_gets(char *buf, size_t size, pr_fh_t *fh) {
char *bp = NULL;
int toread = 0;
pr_buffer_t *pbuf = NULL;
if (!buf || !fh || size <= 0) {
errno = EINVAL;
return NULL;
}
if (!fh->fh_buf) {
fh->fh_buf = pcalloc(fh->fh_pool, sizeof(pr_buffer_t));
fh->fh_buf->buf = fh->fh_buf->current = pcalloc(fh->fh_pool,
PR_TUNABLE_BUFFER_SIZE);
fh->fh_buf->remaining = fh->fh_buf->buflen = PR_TUNABLE_BUFFER_SIZE;
}
pbuf = fh->fh_buf;
bp = buf;
while (size) {
if (!pbuf->current ||
pbuf->remaining == pbuf->buflen) { /* empty buffer */
toread = pr_fsio_read(fh, pbuf->buf,
size < pbuf->buflen ? size : pbuf->buflen);
if (toread <= 0) {
if (bp != buf) {
*bp = '\0';
return buf;
} else
return NULL;
}
pbuf->remaining = pbuf->buflen - toread;
pbuf->current = pbuf->buf;
} else
toread = pbuf->buflen - pbuf->remaining;
while (size && toread > 0 && *pbuf->current != '\n' && toread--) {
*bp++ = *pbuf->current++;
size--;
pbuf->remaining++;
}
if (size && toread && *pbuf->current == '\n') {
size--;
toread--;
*bp++ = *pbuf->current++;
pbuf->remaining++;
break;
}
if (!toread)
pbuf->current = NULL;
}
*bp = '\0';
return buf;
}
/* pr_fsio_getline() is an fgets() with backslash-newline stripping, copied from
* Wietse Venema's tcpwrapppers-7.6 code. The extra *lineno argument is
* needed, at the moment, to properly track which line of the configuration
* file is being read in, so that errors can be reported with line numbers
* correctly.
*/
char *pr_fsio_getline(char *buf, int buflen, pr_fh_t *fh,
unsigned int *lineno) {
int inlen;
char *start = buf;
while (pr_fsio_gets(buf, buflen, fh)) {
/* while() loops should always handle signals. */
pr_signals_handle();
inlen = strlen(buf);
if (inlen >= 1) {
if (buf[inlen - 1] == '\n') {
(*lineno)++;
if (inlen >= 2 && buf[inlen - 2] == '\\') {
char *bufp;
inlen -= 2;
/* Watch for commented lines when handling line continuations.
* Advance past any leading whitespace, to see if the first
* non-whitespace character is the comment character.
*/
for (bufp = buf; *bufp && isspace((int) *bufp); bufp++);
if (*bufp == '#')
continue;
} else
return start;
}
}
/* Be careful of reading too much. */
if (buflen - inlen == 0)
return buf;
buf += inlen;
buflen -= inlen;
buf[0] = 0;
}
return (buf > start ? start : 0);
}
#if defined(HAVE_SYS_STATVFS_H) || defined(HAVE_SYS_VFS_H) || defined(HAVE_STATFS)
/* Simple multiplication and division doesn't work with very large
* filesystems (overflows 32 bits). This code should handle it.
*/
static off_t calc_fs_size(size_t blocks, size_t bsize) {
off_t bl_lo, bl_hi;
off_t res_lo, res_hi, tmp;
bl_lo = blocks & 0x0000ffff;
bl_hi = blocks & 0xffff0000;
tmp = (bl_hi >> 16) * bsize;
res_hi = tmp & 0xffff0000;
res_lo = (tmp & 0x0000ffff) << 16;
res_lo += bl_lo * bsize;
if (res_hi & 0xfc000000)
/* Overflow */
return 0;
return (res_lo >> 10) | (res_hi << 6);
}
off_t pr_fs_getsize(char *path) {
# if defined(HAVE_SYS_STATVFS_H)
# if _FILE_OFFSET_BITS == 64 && defined(SOLARIS2) && \
!defined(SOLARIS2_5_1) && !defined(SOLARIS2_6) && !defined(SOLARIS2_7)
/* Note: somewhere along the way, Sun decided that the prototype for
* its statvfs64(2) function would include a statvfs64_t rather than
* struct statvfs64. In 2.6 and 2.7, it's struct statvfs64, and
* in 8, 9 it's statvfs64_t. This should silence compiler warnings.
* (The statvfs_t will be redefined to a statvfs64_t as appropriate on
* LFS systems).
*/
statvfs_t fs;
# else
struct statvfs fs;
# endif /* LFS && !Solaris 2.5.1 && !Solaris 2.6 && !Solaris 2.7 */
if (statvfs(path, &fs) != 0)
return 0;
return calc_fs_size(fs.f_bavail, fs.f_frsize);
# elif defined(HAVE_SYS_VFS_H)
struct statfs fs;
if (statfs(path, &fs) != 0)
return 0;
return calc_fs_size(fs.f_bavail, fs.f_bsize);
# elif defined(HAVE_STATFS)
struct statfs fs;
if (statfs(path, &fs) != 0)
return 0;
return calc_fs_size(fs.f_bavail, fs.f_bsize);
# endif /* !HAVE_STATFS && !HAVE_SYS_STATVFS && !HAVE_SYS_VFS */
}
#endif /* !HAVE_STATFS && !HAVE_SYS_STATVFS && !HAVE_SYS_VFS */
int pr_fsio_puts(const char *buf, pr_fh_t *fh) {
if (!fh) {
errno = EINVAL;
return -1;
}
return pr_fsio_write(fh, buf, strlen(buf));
}
void pr_resolve_fs_map(void) {
register unsigned int i = 0;
if (!fs_map)
return;
for (i = 0; i < fs_map->nelts; i++) {
char *newpath = NULL;
unsigned char add_slash = FALSE;
pr_fs_t *tmpfs = ((pr_fs_t **) fs_map->elts)[i];
/* Skip if this fs is the root fs. */
if (tmpfs == root_fs)
continue;
/* Note that dir_realpath() does _not_ handle "../blah" paths
* well, so...at least for now, hope that such paths are screened
* by the code adding such paths into the fs_map. Check for
* a trailing slash in the unadjusted path, so that I know if I need
* to re-add that slash to the adjusted path -- these trailing slashes
* are important!
*/
if ((strcmp(tmpfs->fs_path, "/") != 0 &&
(tmpfs->fs_path)[strlen(tmpfs->fs_path) - 1] == '/'))
add_slash = TRUE;
newpath = dir_realpath(tmpfs->fs_pool, tmpfs->fs_path);
if (add_slash)
newpath = pstrcat(tmpfs->fs_pool, newpath, "/", NULL);
/* Note that this does cause a slightly larger memory allocation from
* the pr_fs_t's pool, as the original path value was also allocated
* from that pool, and that original pointer is being overwritten.
* However, as this function is only called once, and that pool
* is freed later, I think this may be acceptable.
*/
tmpfs->fs_path = newpath;
}
/* Resort the map */
qsort(fs_map->elts, fs_map->nelts, sizeof(pr_fs_t *), fs_cmp);
return;
}
int init_fs(void) {
char cwdbuf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
/* Establish the default pr_fs_t that will handle any path */
root_fs = pr_create_fs(permanent_pool, "system");
if (root_fs == NULL) {
/* Do not insert this fs into the FS map. This will allow other
* modules to insert filesystems at "/", if they want.
*/
pr_log_pri(PR_LOG_ERR, "error: unable to initialize default fs");
exit(1);
}
root_fs->fs_path = pstrdup(root_fs->fs_pool, "/");
/* Set the root FSIO handlers. */
root_fs->stat = sys_stat;
root_fs->fstat = sys_fstat;
root_fs->lstat = sys_lstat;
root_fs->rename = sys_rename;
root_fs->unlink = sys_unlink;
root_fs->open = sys_open;
root_fs->creat = sys_creat;
root_fs->close = sys_close;
root_fs->read = sys_read;
root_fs->write = sys_write;
root_fs->lseek = sys_lseek;
root_fs->link = sys_link;
root_fs->readlink = sys_readlink;
root_fs->symlink = sys_symlink;
root_fs->ftruncate = sys_ftruncate;
root_fs->truncate = sys_truncate;
root_fs->chmod = sys_chmod;
root_fs->chown = sys_chown;
root_fs->access = sys_access;
root_fs->faccess = sys_faccess;
root_fs->chdir = sys_chdir;
root_fs->chroot = sys_chroot;
root_fs->opendir = sys_opendir;
root_fs->closedir = sys_closedir;
root_fs->readdir = sys_readdir;
root_fs->mkdir = sys_mkdir;
root_fs->rmdir = sys_rmdir;
if (getcwd(cwdbuf, sizeof(cwdbuf)-1)) {
cwdbuf[sizeof(cwdbuf)-1] = '\0';
pr_fs_setcwd(cwdbuf);
} else {
pr_fsio_chdir("/", FALSE);
pr_fs_setcwd("/");
}
return 0;
}
Last Updated: Thu Feb 23 11:07:15 2006
HTML generated by tj's src2html script