#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <fts.h>
#include <libgen.h>
#include <limits.h>
#include <sys/mount.h>
#include <sys/param.h> // MAXBSIZE, MIN
#include <sys/stat.h>
#include <string.h>
#include <stdio.h> // rename(2)?
#include <stdlib.h> // malloc(3)
#include <sys/types.h>
#include <unistd.h>
#include <sys/ucred.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/kext/OSKextPrivate.h>
#ifndef kOSKextLogCacheFlag
#define kOSKextLogCacheFlag kOSKextLogArchiveFlag
#endif // no kOSKextLogCacheFlag
#define STRICT_SAFETY 0 // since our wrappers need to call the real calls
#include "safecalls.h" // w/o STRICT_SAFETY, will #define mkdir, etc
#include "kext_tools_util.h"
#define RESTOREDIR(savedir) do { if (savedir != -1 && restoredir(savedir)) \
OSKextLog( NULL, \
kOSKextLogErrorLevel | kOSKextLogCacheFlag, \
"%s: ALERT: couldn't restore CWD", __func__); \
} while(0)
#define PATHCPY(dst, src) do { \
\
Boolean useErrno = (errno == 0); \
if (useErrno) errno = ENAMETOOLONG; \
if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX) goto finish; \
if (useErrno) errno = 0; \
} while(0)
#define PATHCAT(dst, src) do { \
COMPILE_TIME_ASSERT(sizeof(dst) == PATH_MAX); \
Boolean useErrno = (errno == 0); \
if (useErrno) errno = ENAMETOOLONG; \
if (strlcat(dst, src, PATH_MAX) >= PATH_MAX) goto finish; \
if (useErrno) errno = 0; \
} while(0)
static int findmnt(dev_t devid, char mntpt[MNAMELEN])
{
int rval = ELAST + 1;
int i, nmnts = getfsstat(NULL, 0, MNT_NOWAIT);
int bufsz;
struct statfs *mounts = NULL;
if (nmnts <= 0) goto finish;
bufsz = nmnts * sizeof(struct statfs);
if (!(mounts = malloc(bufsz))) goto finish;
if (-1 == getfsstat(mounts, bufsz, MNT_NOWAIT)) goto finish;
for (i = 0; i < nmnts; i++) {
struct statfs *sfs = &mounts[i];
if (sfs->f_fsid.val[0] == devid) {
strlcpy(mntpt, sfs->f_mntonname, PATH_MAX);
rval = 0;
break;
}
}
finish:
if (mounts) free(mounts);
return rval;
}
static int spolicy(int scopefd, int candfd)
{
int bsderr = -1;
struct stat candsb, scopesb;
char path[PATH_MAX] = "<unknown>";
if ((bsderr = fstat(candfd, &candsb))) goto finish; if ((bsderr = fstat(scopefd, &scopesb))) goto finish;
if (candsb.st_dev != scopesb.st_dev ) {
bsderr = -1;
errno = EPERM;
char scopemnt[MNAMELEN];
if (findmnt(scopesb.st_dev, scopemnt) == 0) {
(void)fcntl(candfd, F_GETPATH, path);
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogCacheFlag | kOSKextLogFileAccessFlag,
"ALERT: %s does not appear to be on %s.", path, scopemnt);
} else {
OSKextLog(NULL,
kOSKextLogErrorLevel,
"%s - find mount failed: errno %d %s.",
__FUNCTION__, errno, strerror(errno));
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"ALERT: dev_t mismatch (%d != %d).",
candsb.st_dev, scopesb.st_dev);
}
goto finish;
}
if (candsb.st_uid != 0) {
(void)fcntl(candfd, F_GETPATH, path);
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"WARNING: %s: owner not root!", path);
}
finish:
return bsderr;
}
int schdirparent(int fdvol, const char *path, int *olddir, char child[PATH_MAX])
{
int bsderr = -1;
int dirfd = -1, savedir = -1;
char parent[PATH_MAX];
if (olddir) *olddir = -1;
if (!path) goto finish;
PATHCPY(parent, path);
PATHCPY(parent, dirname(parent));
if (-1 == (dirfd = open(parent, O_RDONLY, 0))) goto finish;
errno = 0;
if (spolicy(fdvol, dirfd)) {
if (errno == EPERM)
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"Policy violation opening %s.", parent);
goto finish;
}
if (child) {
PATHCPY(child, path);
PATHCPY(child, basename(child));
}
if (olddir) {
if (-1 == (savedir = open(".", O_RDONLY))) goto finish;
*olddir = savedir;
}
if ((bsderr = fchdir(dirfd))) goto finish;
finish:
if (bsderr) {
if (savedir != -1) close(savedir);
if (olddir && *olddir != -1) close(*olddir);
}
if (dirfd != -1) close(dirfd);
return bsderr;
}
int sopen(int fdvol, const char *path, int flags, mode_t mode )
{
int rfd = -1;
int candfd = -1;
char child[PATH_MAX];
int savedir = -1;
if (flags & O_CREAT)
flags |= O_EXCL | O_NOFOLLOW;
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
if (-1 == (candfd = open(child, flags, mode))) goto finish;
if (spolicy(fdvol, candfd)) goto finish;
rfd = candfd;
finish:
if (candfd != -1 && rfd != candfd) {
close(candfd);
}
RESTOREDIR(savedir);
return rfd;
}
int schdir(int fdvol, const char *path, int *savedir)
{
char cpath[PATH_MAX];
PATHCPY(cpath, path);
PATHCAT(cpath, "/.");
return schdirparent(fdvol, cpath, savedir, NULL);
finish:
return -1;
}
int restoredir(int savedir)
{
int cherr = -1, clerr = -1;
if (savedir != -1) {
cherr = fchdir(savedir);
clerr = close(savedir);
}
return cherr ? cherr : clerr;
}
int smkdir(int fdvol, const char *path, mode_t mode)
{
int bsderr = -1;
int savedir = -1;
char child[PATH_MAX];
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
if ((bsderr = mkdir(child, mode))) goto finish;
finish:
RESTOREDIR(savedir);
return bsderr;
}
int srmdir(int fdvol, const char *path)
{
int bsderr = -1;
char child[PATH_MAX];
int savedir = -1;
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
bsderr = rmdir(child);
finish:
RESTOREDIR(savedir);
return bsderr;
}
int sunlink(int fdvol, const char *path)
{
int bsderr = -1;
char child[PATH_MAX];
int savedir = -1;
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
bsderr = unlink(child);
finish:
RESTOREDIR(savedir);
return bsderr;
}
int srename(int fdvol, const char *oldpath, const char *newpath)
{
int bsderr = -1;
int savedir = -1;
char oldname[PATH_MAX];
char newname[PATH_MAX];
PATHCPY(newname, newpath);
PATHCPY(newname, basename(newname));
if (schdirparent(fdvol, oldpath, &savedir, oldname)) goto finish;
bsderr = rename(oldname, newname);
finish:
RESTOREDIR(savedir);
return bsderr;
}
int szerofile(int fdvol, const char *toErase)
{
int bsderr = -1;
int zfd = -1;
struct stat sb;
off_t bytesLeft;
ssize_t bufsize, thisTime;
void *buf = NULL;
zfd = sopen(fdvol, toErase, O_WRONLY, 0);
if (zfd == -1) {
if (errno == ENOENT)
bsderr = 0;
goto finish;
}
if (fstat(zfd, &sb)) goto finish;
if (sb.st_size == 0) {
bsderr = 0;
goto finish;
}
bufsize = MIN(sb.st_size, MAXBSIZE);
if (!(buf = calloc(1, bufsize))) goto finish;
for (bytesLeft = sb.st_size; bytesLeft > 0; bytesLeft -= thisTime) {
thisTime = MIN(bytesLeft, (unsigned int)bufsize);
if (write(zfd, buf, thisTime) != thisTime) goto finish;
}
(void)ftruncate(zfd, 0LL);
bsderr = 0;
finish:
if (zfd != -1) close(zfd);
if (buf) free(buf);
return bsderr;
}
int sdeepunlink(int fdvol, char *path)
{
int rval = ELAST + 1;
int firstErrno = 0;
char * const pathv[2] = { path, NULL };
int ftsoptions = 0;
FTS * fts;
FTSENT * fent;
ftsoptions |= FTS_PHYSICAL; ftsoptions |= FTS_XDEV; ftsoptions |= FTS_NOSTAT;
rval = -1;
if ((fts = fts_open(pathv, ftsoptions, NULL)) == NULL) goto finish;
rval = 0;
while ((fent = fts_read(fts)) ) {
switch (fent->fts_info) {
case FTS_DC: case FTS_D: case FTS_DOT: break;
case FTS_DNR: case FTS_ERR: case FTS_NS: if (!firstErrno) firstErrno = fent->fts_errno;
break;
case FTS_SL: case FTS_SLNONE: case FTS_DEFAULT: case FTS_F: case FTS_NSOK: default: rval |= sunlink(fdvol, fent->fts_accpath);
if (!firstErrno) firstErrno = errno;
break;
case FTS_DP: rval |= srmdir(fdvol, fent->fts_accpath);
if (!firstErrno) firstErrno = errno;
break;
} }
if (fts_close(fts) < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"fts_close failed - %s.", strerror(errno));
}
if (firstErrno) {
rval = -1;
errno = firstErrno;
}
finish:
if (rval == 0 && errno) {
rval = -1;
}
return rval;
}
int sdeepmkdir(int fdvol, const char *path, mode_t mode)
{
int bsderr = -1;
struct stat sb;
char parent[PATH_MAX];
if (strlen(path) == 0) goto finish;
if (0 == stat(path, &sb)) {
if ((sb.st_mode & S_IFMT) != S_IFDIR) {
bsderr = ENOTDIR;
goto finish;
} else {
bsderr = 0; goto finish;
}
} else if (errno != ENOENT) {
goto finish; } else {
PATHCPY(parent, path);
PATHCPY(parent, dirname(parent));
if ((bsderr = sdeepmkdir(fdvol, parent, mode))) goto finish;
}
bsderr = smkdir(fdvol, path, mode);
finish:
return bsderr;
}
static int
_copyfiledata(int srcfd, struct stat *srcsb, int dstfdvol, const char *dstpath)
{
int bsderr = -1;
int dstfd = -1;
void *buf = NULL; ssize_t bufsize, thisTime;
off_t bytesLeft;
(void)sunlink(dstfdvol, dstpath);
dstfd = sopen(dstfdvol, dstpath, O_CREAT|O_WRONLY, srcsb->st_mode|S_IWUSR);
if (dstfd == -1) goto finish;
bufsize = MIN(srcsb->st_size, MAXBSIZE);
if (!(buf = malloc(bufsize))) goto finish;;
for (bytesLeft = srcsb->st_size; bytesLeft > 0; bytesLeft -= thisTime) {
thisTime = MIN(bytesLeft, (unsigned int)bufsize);
if (read(srcfd, buf, thisTime) != thisTime) goto finish;
if (write(dstfd, buf, thisTime) != thisTime) goto finish;
}
if ((bsderr = fchmod(dstfd, srcsb->st_mode))) goto finish;
finish:
if (dstfd != -1) close(dstfd);
if (buf) free(buf);
return bsderr;
}
static int
_copysubitems(int srcfdvol, const char *srcdir, int dstfdvol,const char *dstdir)
{
int bsderr = -1;
DIR *dir = NULL;
struct dirent dentry, *dp;
char srcpath[PATH_MAX], dstpath[PATH_MAX];
if (!(dir = opendir(srcdir)))
goto finish;
if (spolicy(srcfdvol, dirfd(dir)))
goto finish;
while (0 == (bsderr = readdir_r(dir, &dentry, &dp)) && dp) {
char *fname = dp->d_name;
if ((fname[0] == '.' && fname[1] == '\0') ||
(fname[0] == '.' && fname[1] == '.' && fname[2] == '\0'))
continue;
PATHCPY(srcpath, srcdir);
PATHCAT(srcpath, "/");
PATHCAT(srcpath, fname);
PATHCPY(dstpath, dstdir);
PATHCAT(dstpath, "/");
PATHCAT(dstpath, fname);
bsderr = scopyitem(srcfdvol, srcpath, dstfdvol, dstpath);
if (bsderr) goto finish;
}
finish:
if (dir) closedir(dir);
return bsderr;
}
int
scopyitem(int srcfdvol, const char *srcpath, int dstfdvol, const char *dstpath)
{
int bsderr = -1;
int srcfd = -1;
struct stat srcsb;
char dstparent[PATH_MAX];
mode_t dirmode;
if (-1 == (srcfd = sopen(srcfdvol, srcpath, O_RDONLY, 0))) goto finish;
if (fstat(srcfd, &srcsb)) goto finish;
dirmode = ((srcsb.st_mode&~S_IFMT) | S_IWUSR | S_IXUSR );
if (dirmode & S_IRGRP) dirmode |= S_IXGRP; if (dirmode & S_IROTH) dirmode |= S_IXOTH;
PATHCPY(dstparent, dstpath);
PATHCPY(dstparent, dirname(dstparent));
if ((bsderr = sdeepmkdir(dstfdvol, dstparent, dirmode))) goto finish;
switch ((srcsb.st_mode & S_IFMT)) {
case S_IFREG:
bsderr = _copyfiledata(srcfd, &srcsb, dstfdvol, dstpath);
break;
case S_IFDIR:
bsderr = _copysubitems(srcfdvol, srcpath, dstfdvol, dstpath);
break;
default:
bsderr = EFTYPE;
break;
}
finish:
if (srcfd != -1) close(srcfd);
return bsderr;
}