#include "lint.h"
#include <mtlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <ftw.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <thread.h>
#include <synch.h>
#include <stdio.h>
#include <strings.h>
#include <fcntl.h>
#if !defined(_LP64) && _FILE_OFFSET_BITS == 64
#define nftw nftw64
#define stat stat64
#define fstat fstat64
#define fstatat fstatat64
#pragma weak _nftw64 = nftw64
#else
#pragma weak _nftw = nftw
#endif
#ifndef PATH_MAX
#define PATH_MAX 1023
#endif
struct Save {
struct Save *last;
DIR *fd;
char *comp;
long here;
dev_t dev;
ino_t inode;
};
struct Var {
char *home;
size_t len;
char *fullpath;
char *tmppath;
int curflags;
dev_t cur_mount;
struct FTW state;
int walklevel;
int (*statf)(const char *, struct stat *, struct Save *, int flags);
int (*savedstatf)(const char *, struct stat *, struct Save *,
int flags);
DIR *(*opendirf)(const char *);
};
static int oldclose(struct Save *);
static int cdlstat(const char *, struct stat *, struct Save *, int flags);
static int cdstat(const char *, struct stat *, struct Save *, int flags);
static int nocdlstat(const char *, struct stat *, struct Save *, int flags);
static int nocdstat(const char *, struct stat *, struct Save *, int flags);
static DIR *cdopendir(const char *);
static DIR *nocdopendir(const char *);
static const char *get_unrooted(const char *);
static int
walk(char *component,
int (*fn)(const char *, const struct stat *, int, struct FTW *),
int depth, struct Save *last, struct Var *vp)
{
struct stat statb;
char *p, *tmp;
int type;
char *comp;
struct dirent *dir;
char *q;
int rc = 0;
int val = -1;
int cdval = -1;
int oldbase;
int skip;
struct Save this;
size_t base_comp, base_component, base_this_comp, base_last_comp;
size_t base_fullpath, base_tmppath;
this.last = last;
this.fd = 0;
if ((vp->curflags & FTW_CHDIR) && last)
comp = last->comp;
else
comp = vp->tmppath;
if (vp->savedstatf == NULL)
vp->savedstatf = vp->statf;
if ((vp->walklevel++ == 0) && (vp->curflags & FTW_HOPTION)) {
if (((vp->curflags & FTW_CHDIR) == 0) && (depth >= 2)) {
vp->statf = nocdstat;
} else {
vp->statf = cdstat;
}
} else {
vp->statf = vp->savedstatf;
}
if ((*vp->statf)(comp, &statb, last, _AT_TRIGGER) >= 0) {
if ((statb.st_mode & S_IFMT) == S_IFDIR) {
type = FTW_D;
if (depth <= 1)
(void) oldclose(last);
if ((this.fd = (*vp->opendirf)(comp)) == 0) {
if (errno == EMFILE && oldclose(last) &&
(this.fd = (*vp->opendirf)(comp)) != 0) {
depth = 1;
} else {
type = FTW_DNR;
goto fail;
}
}
} else if ((statb.st_mode & S_IFMT) == S_IFLNK) {
type = FTW_SL;
} else {
type = FTW_F;
}
} else if ((vp->curflags & FTW_ANYERR) && errno != ENOENT) {
type = FTW_NS;
goto fail;
} else {
if (((vp->statf == cdstat) &&
(cdlstat(comp, &statb, last, 0) >= 0) &&
((statb.st_mode & S_IFMT) == S_IFLNK)) ||
((vp->statf == nocdstat) &&
(nocdlstat(comp, &statb, last, 0) >= 0) &&
((statb.st_mode & S_IFMT) == S_IFLNK))) {
errno = ENOENT;
type = FTW_SLN;
} else {
type = FTW_NS;
fail:
if (!(vp->curflags & FTW_ANYERR))
if (errno != EACCES)
return (-1);
}
}
if ((vp->curflags & FTW_MOUNT) && type != FTW_NS &&
statb.st_dev != vp->cur_mount)
goto quit;
vp->state.quit = 0;
if (type != FTW_D || (vp->curflags & FTW_DEPTH) == 0)
rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
if (rc > 0)
val = rc;
skip = (vp->state.quit & FTW_SKD);
if (rc != 0 || type != FTW_D || (vp->state.quit & FTW_PRUNE))
goto quit;
if (vp->tmppath[0] != '\0' && component[-1] != '/')
*component++ = '/';
*component = 0;
if (vp->curflags & FTW_CHDIR) {
struct stat statb2;
if ((vp->curflags & FTW_PHYS) &&
(fstat(this.fd->dd_fd, &statb2) < 0 ||
statb2.st_ino != statb.st_ino ||
statb2.st_dev != statb.st_dev)) {
errno = EAGAIN;
rc = -1;
goto quit;
}
if ((cdval = fchdir(this.fd->dd_fd)) >= 0) {
this.comp = component;
} else {
type = FTW_DNR;
rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
goto quit;
}
}
if ((vp->curflags & FTW_NOLOOP) ||
((vp->curflags & FTW_PHYS) == 0)) {
struct Save *sp = last;
while (sp) {
if (sp->dev == statb.st_dev &&
sp->inode == statb.st_ino) {
if (vp->curflags & FTW_NOLOOP) {
type = FTW_DL;
goto fail;
}
goto quit;
}
sp = sp->last;
}
}
this.dev = statb.st_dev;
this.inode = statb.st_ino;
oldbase = vp->state.base;
vp->state.base = (int)(component - vp->tmppath);
while ((dir = readdir(this.fd)) != NULL) {
if (dir->d_ino == 0)
continue;
q = dir->d_name;
if (*q == '.') {
if (q[1] == 0)
continue;
else if (q[1] == '.' && q[2] == 0)
continue;
}
if (last != NULL && last->comp != NULL) {
base_last_comp = last->comp - vp->home;
}
base_comp = comp - vp->home;
base_component = component - vp->home;
if ((strlen(q) + strlen(vp->home) + 1) > vp->len) {
base_this_comp = this.comp - vp->home;
base_fullpath = vp->fullpath - vp->home;
base_tmppath = vp->tmppath - vp->home;
vp->len *= 2;
tmp = (char *)realloc(vp->home, vp->len);
if (tmp == NULL) {
rc = -1;
goto quit;
}
vp->home = tmp;
comp = vp->home + base_comp;
component = vp->home + base_component;
this.comp = vp->home + base_this_comp;
vp->fullpath = vp->home + base_fullpath;
vp->tmppath = vp->home + base_tmppath;
if (last != NULL && last->comp != NULL) {
last->comp = vp->home + base_last_comp;
}
}
p = component;
while (*q != '\0')
*p++ = *q++;
*p = '\0';
vp->state.level++;
rc = walk(p, fn, depth-1, &this, vp);
if (last != NULL && last->comp != NULL) {
last->comp = vp->home + base_last_comp;
}
comp = vp->home + base_comp;
component = vp->home + base_component;
vp->state.level--;
if (this.fd == 0) {
*component = 0;
if (vp->curflags & FTW_CHDIR) {
this.fd = opendir(".");
} else {
this.fd = (*vp->opendirf)(comp);
}
if (this.fd == 0) {
rc = -1;
goto quit;
}
seekdir(this.fd, this.here);
}
if (rc != 0) {
if (errno == ENOENT) {
(void) fprintf(stderr, "cannot open %s: %s\n",
vp->tmppath, strerror(errno));
val = rc;
continue;
}
goto quit;
}
}
vp->state.base = oldbase;
*--component = 0;
type = FTW_DP;
if ((vp->tmppath[0] != '\0') && (vp->curflags & FTW_DEPTH) && !skip)
rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
quit:
if (cdval >= 0 && last) {
if (last->fd != NULL) {
if (fchdir(last->fd->dd_fd) < 0) {
rc = -1;
}
} else {
if ((cdval = chdir("..")) >= 0) {
if ((*vp->statf)(".", &statb, last, 0) < 0 ||
statb.st_ino != last->inode ||
statb.st_dev != last->dev)
cdval = -1;
}
*comp = 0;
if (cdval < 0) {
if (chdir(vp->fullpath) < 0) {
rc = -1;
} else {
if ((vp->curflags & FTW_PHYS) &&
((*vp->statf)(".", &statb,
last, 0) < 0 ||
statb.st_ino != last->inode ||
statb.st_dev != last->dev)) {
errno = EAGAIN;
rc = -1;
}
}
}
}
}
if (this.fd)
(void) closedir(this.fd);
if (val > rc)
return (val);
else
return (rc);
}
int
nftw(const char *path,
int (*fn)(const char *, const struct stat *, int, struct FTW *),
int depth, int flags)
{
struct Var var;
struct stat statb;
int rc = -1;
char *dp;
char *base;
char *endhome;
const char *savepath = path;
int save_errno;
var.walklevel = 0;
var.len = 2*(PATH_MAX+1);
var.home = (char *)malloc(var.len);
if (var.home == NULL)
return (-1);
var.home[0] = 0;
if (flags & FTW_CHDIR) {
if (getcwd(var.home, PATH_MAX+1) == 0) {
free(var.home);
return (-1);
}
}
endhome = dp = var.home + strlen(var.home);
if (*path == '/')
var.fullpath = dp;
else {
*dp++ = '/';
var.fullpath = var.home;
}
var.tmppath = dp;
base = dp-1;
while (*path) {
*dp = *path;
if (*dp == '/')
base = dp;
dp++, path++;
}
*dp = 0;
var.state.base = (int)(base + 1 - var.tmppath);
if (*path) {
free(var.home);
errno = ENAMETOOLONG;
return (-1);
}
var.curflags = flags;
if (((flags & FTW_CHDIR) == 0) && (depth >= 2)) {
var.opendirf = nocdopendir;
if (flags & FTW_PHYS)
var.statf = nocdlstat;
else
var.statf = nocdstat;
} else {
var.opendirf = cdopendir;
if (flags & FTW_PHYS)
var.statf = cdlstat;
else
var.statf = cdstat;
}
if (flags & FTW_MOUNT) {
if ((*var.statf)(savepath, &statb, NULL, 0) >= 0)
var.cur_mount = statb.st_dev;
else
goto done;
}
var.state.level = 0;
save_errno = errno;
errno = 0;
var.savedstatf = NULL;
rc = walk(dp, fn, depth, (struct Save *)0, &var);
if (errno == 0)
errno = save_errno;
done:
*endhome = 0;
if (flags & FTW_CHDIR)
(void) chdir(var.home);
free(var.home);
return (rc);
}
static int
cdstat(const char *path, struct stat *statp, struct Save *lp __unused,
int flags)
{
return (fstatat(AT_FDCWD, path, statp, flags));
}
static int
cdlstat(const char *path, struct stat *statp, struct Save *lp __unused,
int flags)
{
return (fstatat(AT_FDCWD, path, statp,
flags | AT_SYMLINK_NOFOLLOW));
}
static int
nocdstat(const char *path, struct stat *statp, struct Save *lp, int flags)
{
int fd;
const char *basepath;
if (lp && lp->fd) {
basepath = get_unrooted(path);
fd = lp->fd->dd_fd;
} else {
basepath = path;
fd = AT_FDCWD;
}
return (fstatat(fd, basepath, statp, flags));
}
static int
nocdlstat(const char *path, struct stat *statp, struct Save *lp, int flags)
{
int fd;
const char *basepath;
if (lp && lp->fd) {
basepath = get_unrooted(path);
fd = lp->fd->dd_fd;
} else {
basepath = path;
fd = AT_FDCWD;
}
return (fstatat(fd, basepath, statp, flags | AT_SYMLINK_NOFOLLOW));
}
static DIR *
cdopendir(const char *path)
{
return (opendir(path));
}
static DIR *
nocdopendir(const char *path)
{
int fd, cfd;
DIR *fdd;
char *dirp, *token, *ptr;
if (((fdd = opendir(path)) == NULL) && (errno == ENAMETOOLONG)) {
if ((dirp = strdup(path)) == NULL) {
errno = ENAMETOOLONG;
return (NULL);
}
if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
(void) free(dirp);
errno = ENAMETOOLONG;
return (NULL);
}
while ((token = strtok_r(NULL, "/", &ptr)) != NULL) {
if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
(void) close(fd);
(void) free(dirp);
errno = ENAMETOOLONG;
return (NULL);
}
(void) close(fd);
fd = cfd;
}
(void) free(dirp);
return (fdopendir(fd));
}
(void) free(dirp);
errno = ENAMETOOLONG;
}
return (fdd);
}
static const char *
get_unrooted(const char *path)
{
const char *ptr;
if (!path || !*path)
return (NULL);
ptr = path + strlen(path);
while (ptr != path && *--ptr == '/')
;
if (ptr == path)
return (ptr);
while (ptr != path)
if (*--ptr == '/')
return (++ptr);
return (ptr);
}
static int
oldclose(struct Save *sp)
{
struct Save *spnext;
while (sp) {
spnext = sp->last;
if (spnext == 0 || spnext->fd == 0)
break;
sp = spnext;
}
if (sp == 0 || sp->fd == 0)
return (0);
sp->here = telldir(sp->fd);
(void) closedir(sp->fd);
sp->fd = 0;
return (1);
}