From 50432108fb3ef826301626b94c5e82ad2ab2bd75 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 8 Jan 2018 21:43:23 -0500 Subject: stat: New wrapper around the stat() family This lets bfs transparently support the new statx() system call on Linux, giving it access to file birth times. --- Makefile | 2 +- bftw.c | 27 ++++--- bftw.h | 6 +- color.c | 23 +++--- eval.c | 176 +++++++++++++++++++++------------------------ expr.h | 19 +---- mtab.c | 24 ++++--- mtab.h | 6 +- parse.c | 109 ++++++++++++---------------- printf.c | 92 +++++++++++++----------- printf.h | 2 +- stat.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ stat.h | 94 ++++++++++++++++++++++++ util.c | 11 --- util.h | 30 +------- 15 files changed, 565 insertions(+), 299 deletions(-) create mode 100644 stat.c create mode 100644 stat.h diff --git a/Makefile b/Makefile index 411128c..e3eadb1 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ ALL_LDFLAGS = $(ALL_CFLAGS) $(LDFLAGS) all: bfs -bfs: bftw.o color.o dstring.o eval.o exec.o main.o mtab.o opt.o parse.o printf.o typo.o util.o +bfs: bftw.o color.o dstring.o eval.o exec.o main.o mtab.o opt.o parse.o printf.o stat.o typo.o util.o $(CC) $(ALL_LDFLAGS) $^ -o $@ release: CFLAGS := -O3 -flto $(WFLAGS) -DNDEBUG -g diff --git a/bftw.c b/bftw.c index f52d4e2..82d216b 100644 --- a/bftw.c +++ b/bftw.c @@ -35,6 +35,7 @@ #include "bftw.h" #include "dstring.h" +#include "stat.h" #include "util.h" #include #include @@ -45,8 +46,6 @@ #include #include #include -#include -#include #include /** @@ -550,11 +549,11 @@ static void bftw_queue_destroy(struct bftw_queue *queue) { } /** Call stat() and use the results. */ -static int ftwbuf_stat(struct BFTW *ftwbuf, struct stat *sb) { - int ret = xfstatat(ftwbuf->at_fd, ftwbuf->at_path, sb, ftwbuf->at_flags); +static int ftwbuf_stat(struct BFTW *ftwbuf, struct bfs_stat *sb) { + int ret = bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, BFS_STAT_BROKEN_OK, sb); if (ret == 0) { ftwbuf->statbuf = sb; - ftwbuf->typeflag = mode_to_typeflag(sb->st_mode); + ftwbuf->typeflag = mode_to_typeflag(sb->mode); } return ret; } @@ -606,8 +605,8 @@ struct bftw_state { /** Extra data about the current file. */ struct BFTW ftwbuf; - /** stat() buffer for the current file. */ - struct stat statbuf; + /** bfs_stat() buffer for the current file. */ + struct bfs_stat statbuf; }; /** @@ -840,8 +839,8 @@ static void bftw_init_buffers(struct bftw_state *state, const struct dirent *de) } if (ftwbuf->typeflag == BFTW_DIR && detect_cycles) { - dev_t dev = ftwbuf->statbuf->st_dev; - ino_t ino = ftwbuf->statbuf->st_ino; + dev_t dev = ftwbuf->statbuf->dev; + ino_t ino = ftwbuf->statbuf->ino; for (const struct bftw_dir *dir = current; dir; dir = dir->parent) { if (dev == dir->dev && ino == dir->ino) { bftw_set_error(state, ELOOP); @@ -890,10 +889,10 @@ static struct bftw_dir *bftw_add(struct bftw_state *state, const char *name) { return NULL; } - const struct stat *statbuf = state->ftwbuf.statbuf; + const struct bfs_stat *statbuf = state->ftwbuf.statbuf; if (statbuf) { - dir->dev = statbuf->st_dev; - dir->ino = statbuf->st_ino; + dir->dev = statbuf->dev; + dir->ino = statbuf->ino; } return dir; @@ -1083,10 +1082,10 @@ int bftw(const char *path, bftw_fn *fn, int nopenfd, enum bftw_flags flags, void } if (state.ftwbuf.typeflag == BFTW_DIR) { - const struct stat *statbuf = state.ftwbuf.statbuf; + const struct bfs_stat *statbuf = state.ftwbuf.statbuf; if ((flags & BFTW_XDEV) && statbuf - && statbuf->st_dev != state.current->dev) { + && statbuf->dev != state.current->dev) { continue; } diff --git a/bftw.h b/bftw.h index 77148f1..ebdd5d3 100644 --- a/bftw.h +++ b/bftw.h @@ -17,8 +17,8 @@ #ifndef BFS_BFTW_H #define BFS_BFTW_H +#include "stat.h" #include -#include /** * Possible file types. @@ -81,8 +81,8 @@ struct BFTW { /** The errno that occurred, if typeflag == BFTW_ERROR. */ int error; - /** A stat() buffer; may be NULL if no stat() call was needed. */ - const struct stat *statbuf; + /** A bfs_stat() buffer; may be NULL if no stat() call was needed. */ + const struct bfs_stat *statbuf; /** A parent file descriptor for the *at() family of calls. */ int at_fd; diff --git a/color.c b/color.c index 594c5c9..cb73d3f 100644 --- a/color.c +++ b/color.c @@ -16,6 +16,7 @@ #include "color.h" #include "bftw.h" +#include "stat.h" #include "util.h" #include #include @@ -296,24 +297,24 @@ int cfclose(CFILE *cfile) { } static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf) { - const struct stat *sb = ftwbuf->statbuf; + const struct bfs_stat *sb = ftwbuf->statbuf; if (!sb) { return colors->orphan; } const char *color = NULL; - switch (sb->st_mode & S_IFMT) { + switch (sb->mode & S_IFMT) { case S_IFREG: - if (sb->st_mode & S_ISUID) { + if (sb->mode & S_ISUID) { color = colors->setuid; - } else if (sb->st_mode & S_ISGID) { + } else if (sb->mode & S_ISGID) { color = colors->setgid; - } else if (sb->st_mode & 0111) { + } else if (sb->mode & 0111) { color = colors->exec; } - if (!color && sb->st_nlink > 1) { + if (!color && sb->nlink > 1) { color = colors->multi_hard; } @@ -335,13 +336,13 @@ static const char *file_color(const struct colors *colors, const char *filename, break; case S_IFDIR: - if (sb->st_mode & S_ISVTX) { - if (sb->st_mode & S_IWOTH) { + if (sb->mode & S_ISVTX) { + if (sb->mode & S_IWOTH) { color = colors->sticky_ow; } else { color = colors->sticky; } - } else if (sb->st_mode & S_IWOTH) { + } else if (sb->mode & S_IWOTH) { color = colors->ow; } @@ -455,8 +456,8 @@ static int print_link(CFILE *cfile, const struct BFTW *ftwbuf) { altbuf.path = target; altbuf.nameoff = xbasename(target) - target; - struct stat statbuf; - if (fstatat(ftwbuf->at_fd, ftwbuf->at_path, &statbuf, 0) == 0) { + struct bfs_stat statbuf; + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, 0, 0, &statbuf) == 0) { altbuf.statbuf = &statbuf; } else { altbuf.statbuf = NULL; diff --git a/eval.c b/eval.c index 88616cc..343c3f7 100644 --- a/eval.c +++ b/eval.c @@ -22,6 +22,7 @@ #include "exec.h" #include "mtab.h" #include "printf.h" +#include "stat.h" #include "util.h" #include #include @@ -51,7 +52,9 @@ struct eval_state { /** Whether to quit immediately. */ bool *quit; /** A stat() buffer, if necessary. */ - struct stat statbuf; + struct bfs_stat statbuf; + /** A bfs_stat() buffer, if necessary. */ + struct bfs_stat bfs_statbuf; }; /** @@ -74,13 +77,13 @@ static void eval_error(struct eval_state *state) { } /** - * Perform a stat() call if necessary. + * Perform a bfs_stat() call if necessary. */ -static const struct stat *fill_statbuf(struct eval_state *state) { +static const struct bfs_stat *eval_bfs_stat(struct eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; if (!ftwbuf->statbuf) { - if (xfstatat(ftwbuf->at_fd, ftwbuf->at_path, &state->statbuf, ftwbuf->at_flags) == 0) { - ftwbuf->statbuf = &state->statbuf; + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, BFS_STAT_BROKEN_OK, &state->bfs_statbuf) == 0) { + ftwbuf->statbuf = &state->bfs_statbuf; } else { eval_error(state); } @@ -135,34 +138,42 @@ bool eval_access(const struct expr *expr, struct eval_state *state) { } /** - * -[aBcm]?newer tests. + * Get the given timespec field out of a stat buffer. */ -bool eval_newer(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); +static const struct timespec *eval_stat_time(const struct expr *expr, struct eval_state *state) { + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { - return false; + return NULL; } - const struct timespec *time; - switch (expr->time_field) { - case ATIME: - time = &statbuf->st_atim; - break; - case CTIME: - time = &statbuf->st_ctim; - break; - case MTIME: - time = &statbuf->st_mtim; - break; - -#if BFS_HAVE_ST_BIRTHTIM - case BTIME: - time = &statbuf->st_birthtim; - break; -#endif - + if (!(statbuf->mask & expr->stat_field)) { + assert(expr->stat_field == BFS_STAT_BTIME); + cfprintf(state->cmdline->cerr, "%{er}error: '%s': Couldn't get file birth time.%{rs}\n", state->ftwbuf->path); + *state->ret = EXIT_FAILURE; + return NULL; + } + + switch (expr->stat_field) { + case BFS_STAT_ATIME: + return &statbuf->atime; + case BFS_STAT_BTIME: + return &statbuf->btime; + case BFS_STAT_CTIME: + return &statbuf->ctime; + case BFS_STAT_MTIME: + return &statbuf->mtime; default: assert(false); + return NULL; + } +} + +/** + * -[aBcm]?newer tests. + */ +bool eval_newer(const struct expr *expr, struct eval_state *state) { + const struct timespec *time = eval_stat_time(expr, state); + if (!time) { return false; } @@ -174,31 +185,8 @@ bool eval_newer(const struct expr *expr, struct eval_state *state) { * -[aBcm]{min,time} tests. */ bool eval_time(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); - if (!statbuf) { - return false; - } - - const struct timespec *time; - switch (expr->time_field) { - case ATIME: - time = &statbuf->st_atim; - break; - case CTIME: - time = &statbuf->st_ctim; - break; - case MTIME: - time = &statbuf->st_mtim; - break; - -#if BFS_HAVE_ST_BIRTHTIM - case BTIME: - time = &statbuf->st_birthtim; - break; -#endif - - default: - assert(false); + const struct timespec *time = eval_stat_time(expr, state); + if (!time) { return false; } @@ -219,12 +207,12 @@ bool eval_time(const struct expr *expr, struct eval_state *state) { * -used test. */ bool eval_used(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - time_t diff = timespec_diff(&statbuf->st_atim, &statbuf->st_ctim); + time_t diff = timespec_diff(&statbuf->atime, &statbuf->ctime); diff /= 60*60*24; return expr_cmp(expr, diff); } @@ -233,48 +221,48 @@ bool eval_used(const struct expr *expr, struct eval_state *state) { * -gid test. */ bool eval_gid(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return expr_cmp(expr, statbuf->st_gid); + return expr_cmp(expr, statbuf->gid); } /** * -uid test. */ bool eval_uid(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return expr_cmp(expr, statbuf->st_uid); + return expr_cmp(expr, statbuf->uid); } /** * -nogroup test. */ bool eval_nogroup(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return getgrgid(statbuf->st_gid) == NULL; + return getgrgid(statbuf->gid) == NULL; } /** * -nouser test. */ bool eval_nouser(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return getpwuid(statbuf->st_uid) == NULL; + return getpwuid(statbuf->uid) == NULL; } /** @@ -390,9 +378,9 @@ bool eval_empty(const struct expr *expr, struct eval_state *state) { done_dir: closedir(dir); } else { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (statbuf) { - ret = statbuf->st_size == 0; + ret = statbuf->size == 0; } } @@ -404,7 +392,7 @@ done: * -fstype test. */ bool eval_fstype(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } @@ -437,24 +425,24 @@ bool eval_nohidden(const struct expr *expr, struct eval_state *state) { * -inum test. */ bool eval_inum(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return expr_cmp(expr, statbuf->st_ino); + return expr_cmp(expr, statbuf->ino); } /** * -links test. */ bool eval_links(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return expr_cmp(expr, statbuf->st_nlink); + return expr_cmp(expr, statbuf->nlink); } /** @@ -469,12 +457,12 @@ bool eval_lname(const struct expr *expr, struct eval_state *state) { goto done; } - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { goto done; } - name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, statbuf->st_size); + name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, statbuf->size); if (!name) { eval_error(state); goto done; @@ -526,12 +514,12 @@ bool eval_path(const struct expr *expr, struct eval_state *state) { * -perm test. */ bool eval_perm(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - mode_t mode = statbuf->st_mode; + mode_t mode = statbuf->mode; mode_t target; if (state->ftwbuf->typeflag == BFTW_DIR) { target = expr->dir_mode; @@ -560,21 +548,21 @@ bool eval_fls(const struct expr *expr, struct eval_state *state) { CFILE *cfile = expr->cfile; FILE *file = cfile->file; const struct BFTW *ftwbuf = state->ftwbuf; - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { goto done; } - uintmax_t ino = statbuf->st_ino; - uintmax_t blocks = (statbuf->st_blocks + 1)/2; + uintmax_t ino = statbuf->ino; + uintmax_t blocks = (statbuf->blocks + 1)/2; char mode[11]; - format_mode(statbuf->st_mode, mode); - uintmax_t nlink = statbuf->st_nlink; + format_mode(statbuf->mode, mode); + uintmax_t nlink = statbuf->nlink; if (fprintf(file, "%9ju %6ju %s %3ju ", ino, blocks, mode, nlink) < 0) { goto error; } - uintmax_t uid = statbuf->st_uid; + uintmax_t uid = statbuf->uid; struct passwd *pwd = getpwuid(uid); if (pwd) { if (fprintf(file, " %-8s", pwd->pw_name) < 0) { @@ -586,7 +574,7 @@ bool eval_fls(const struct expr *expr, struct eval_state *state) { } } - uintmax_t gid = statbuf->st_gid; + uintmax_t gid = statbuf->gid; struct group *grp = getgrgid(gid); if (grp) { if (fprintf(file, " %-8s", grp->gr_name) < 0) { @@ -598,12 +586,12 @@ bool eval_fls(const struct expr *expr, struct eval_state *state) { } } - uintmax_t size = statbuf->st_size; + uintmax_t size = statbuf->size; if (fprintf(file, " %8ju", size) < 0) { goto error; } - time_t time = statbuf->st_mtim.tv_sec; + time_t time = statbuf->mtime.tv_sec; time_t now = expr->reftime.tv_sec; time_t six_months_ago = now - 6*30*24*60*60; time_t tomorrow = now + 24*60*60; @@ -652,7 +640,7 @@ error: bool eval_fprint(const struct expr *expr, struct eval_state *state) { CFILE *cfile = expr->cfile; if (cfile->colors) { - fill_statbuf(state); + eval_bfs_stat(state); } if (cfprintf(cfile, "%P\n", state->ftwbuf) < 0) { @@ -679,7 +667,7 @@ bool eval_fprint0(const struct expr *expr, struct eval_state *state) { */ bool eval_fprintf(const struct expr *expr, struct eval_state *state) { if (expr->printf->needs_stat) { - if (!fill_statbuf(state)) { + if (!eval_bfs_stat(state)) { goto done; } } @@ -784,19 +772,19 @@ bool eval_regex(const struct expr *expr, struct eval_state *state) { * -samefile test. */ bool eval_samefile(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - return statbuf->st_dev == expr->dev && statbuf->st_ino == expr->ino; + return statbuf->dev == expr->dev && statbuf->ino == expr->ino; } /** * -size test. */ bool eval_size(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } @@ -813,7 +801,7 @@ bool eval_size(const struct expr *expr, struct eval_state *state) { }; off_t scale = scales[expr->size_unit]; - off_t size = (statbuf->st_size + scale - 1)/scale; // Round up + off_t size = (statbuf->size + scale - 1)/scale; // Round up return expr_cmp(expr, size); } @@ -821,13 +809,13 @@ bool eval_size(const struct expr *expr, struct eval_state *state) { * -sparse test. */ bool eval_sparse(const struct expr *expr, struct eval_state *state) { - const struct stat *statbuf = fill_statbuf(state); + const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } - blkcnt_t expected = (statbuf->st_size + 511)/512; - return statbuf->st_blocks < expected; + blkcnt_t expected = (statbuf->size + 511)/512; + return statbuf->blocks < expected; } /** @@ -852,8 +840,8 @@ bool eval_xtype(const struct expr *expr, struct eval_state *state) { // -xtype does the opposite of everything else int at_flags = ftwbuf->at_flags ^ AT_SYMLINK_NOFOLLOW; - struct stat sb; - if (fstatat(ftwbuf->at_fd, ftwbuf->at_path, &sb, at_flags) != 0) { + struct bfs_stat sb; + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, at_flags, 0, &sb) != 0) { if (!follow && is_nonexistence_error(errno)) { // Broken symlink return eval_type(expr, state); @@ -863,7 +851,7 @@ bool eval_xtype(const struct expr *expr, struct eval_state *state) { } } - return mode_to_typeflag(sb.st_mode) & expr->idata; + return mode_to_typeflag(sb.mode) & expr->idata; } #if _POSIX_MONOTONIC_CLOCK > 0 diff --git a/expr.h b/expr.h index 5eff72f..9644a0d 100644 --- a/expr.h +++ b/expr.h @@ -20,6 +20,7 @@ #include "color.h" #include "exec.h" #include "printf.h" +#include "stat.h" #include #include #include @@ -72,20 +73,6 @@ enum mode_cmp { MODE_ANY, }; -/** - * Available struct stat time fields. - */ -enum time_field { - /** Access time. */ - ATIME, - /** Status change time. */ - CTIME, - /** Modification time. */ - MTIME, - /** Birth time. */ - BTIME, -}; - /** * Possible time units. */ @@ -160,10 +147,10 @@ struct expr { /** Mode to use for directories (different due to X). */ mode_t dir_mode; + /** The optional stat field to look at. */ + enum bfs_stat_field stat_field; /** The optional reference time. */ struct timespec reftime; - /** The optional time field. */ - enum time_field time_field; /** The optional time unit. */ enum time_unit time_unit; diff --git a/mtab.c b/mtab.c index fb3f85b..e04f68f 100644 --- a/mtab.c +++ b/mtab.c @@ -15,7 +15,9 @@ ****************************************************************************/ #include "mtab.h" +#include "util.h" #include +#include #include #include #include @@ -110,12 +112,12 @@ struct bfs_mtab *parse_bfs_mtab() { struct mntent *mnt; while ((mnt = getmntent(file))) { - struct stat sb; - if (stat(mnt->mnt_dir, &sb) != 0) { + struct bfs_stat sb; + if (bfs_stat(AT_FDCWD, mnt->mnt_dir, 0, 0, &sb) != 0) { continue; } - if (bfs_mtab_push(mtab, sb.st_dev, mnt->mnt_type) != 0) { + if (bfs_mtab_push(mtab, sb.dev, mnt->mnt_type) != 0) { goto fail_mtab; } } @@ -151,12 +153,12 @@ fail: mtab->capacity = size; for (struct statfs *mnt = mntbuf; mnt < mntbuf + size; ++mnt) { - struct stat sb; - if (stat(mnt->f_mntonname, &sb) != 0) { + struct bfs_stat sb; + if (bfs_stat(AT_FDCWD, mnt->f_mntonname, 0, 0, &sb) != 0) { continue; } - if (bfs_mtab_push(mtab, sb.st_dev, mnt->f_fstypename) != 0) { + if (bfs_mtab_push(mtab, sb.dev, mnt->f_fstypename) != 0) { goto fail_mtab; } } @@ -185,12 +187,12 @@ fail: struct mnttab mnt; while (getmntent(file, &mnt) == 0) { - struct stat sb; - if (stat(mnt.mnt_mountp, &sb) != 0) { + struct bfs_stat sb; + if (bfs_stat(AT_FDCWD, mnt.mnt_mountp, 0, 0, &sb) != 0) { continue; } - if (bfs_mtab_push(mtab, sb.st_dev, mnt.mnt_fstype) != 0) { + if (bfs_mtab_push(mtab, sb.dev, mnt.mnt_fstype) != 0) { goto fail_mtab; } } @@ -212,9 +214,9 @@ fail: #endif } -const char *bfs_fstype(const struct bfs_mtab *mtab, const struct stat *statbuf) { +const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf) { for (struct bfs_mtab_entry *mnt = mtab->table; mnt < mtab->table + mtab->size; ++mnt) { - if (statbuf->st_dev == mnt->dev) { + if (statbuf->dev == mnt->dev) { return mnt->type; } } diff --git a/mtab.h b/mtab.h index cb29fa3..f23c036 100644 --- a/mtab.h +++ b/mtab.h @@ -17,7 +17,7 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include +#include "stat.h" /** * A file system mount table. @@ -37,11 +37,11 @@ struct bfs_mtab *parse_bfs_mtab(void); * @param mtab * The current mount table. * @param statbuf - * The stat() buffer for the file in question. + * The bfs_stat() buffer for the file in question. * @return The type of file system containing this file, "unknown" if not known, * or NULL on error. */ -const char *bfs_fstype(const struct bfs_mtab *mtab, const struct stat *statbuf); +const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf); /** * Free a mount table. diff --git a/parse.c b/parse.c index bfc93af..e71aa47 100644 --- a/parse.c +++ b/parse.c @@ -22,6 +22,7 @@ #include "expr.h" #include "mtab.h" #include "printf.h" +#include "stat.h" #include "typo.h" #include "util.h" #include @@ -389,14 +390,14 @@ static int expr_open(struct parser_state *state, struct expr *expr, const char * goto out; } - struct stat sb; - if (fstat(fileno(cfile->file), &sb) != 0) { + struct bfs_stat sb; + if (bfs_fstat(fileno(cfile->file), &sb) != 0) { cfprintf(cmdline->cerr, "%{er}error: '%s': %m%{rs}\n", path); goto out_close; } for (struct open_file *ofile = cmdline->open_files; ofile; ofile = ofile->next) { - if (ofile->dev == sb.st_dev && ofile->ino == sb.st_ino) { + if (ofile->dev == sb.dev && ofile->ino == sb.ino) { expr->cfile = ofile->cfile; ret = 0; goto out_close; @@ -410,8 +411,8 @@ static int expr_open(struct parser_state *state, struct expr *expr, const char * ofile->cfile = cfile; ofile->path = path; - ofile->dev = sb.st_dev; - ofile->ino = sb.st_ino; + ofile->dev = sb.dev; + ofile->ino = sb.ino; ofile->next = cmdline->open_files; cmdline->open_files = ofile; ++cmdline->nopen_files; @@ -428,15 +429,15 @@ out: } /** - * Invoke stat() on an argument. + * Invoke bfs_stat() on an argument. */ -static int stat_arg(const struct parser_state *state, struct expr *expr, struct stat *sb) { +static int stat_arg(const struct parser_state *state, struct expr *expr, struct bfs_stat *sb) { const struct cmdline *cmdline = state->cmdline; bool follow = cmdline->flags & (BFTW_COMFOLLOW | BFTW_LOGICAL); - int flags = follow ? 0 : AT_SYMLINK_NOFOLLOW; + int at_flags = follow ? 0 : AT_SYMLINK_NOFOLLOW; - int ret = xfstatat(AT_FDCWD, expr->sdata, sb, flags); + int ret = bfs_stat(AT_FDCWD, expr->sdata, at_flags, BFS_STAT_BROKEN_OK, sb); if (ret != 0) { cfprintf(cmdline->cerr, "%{er}error: '%s': %m%{rs}\n", expr->sdata); } @@ -911,21 +912,14 @@ static struct expr *parse_newer(struct parser_state *state, int field, int arg2) return NULL; } -#if !BFS_HAVE_ST_BIRTHTIM - if (field == BTIME) { - cfprintf(state->cmdline->cerr, "%{er}error: %s: File birth times are not supported on this platform.%{rs}\n", expr->argv[0]); - goto fail; - } -#endif - - struct stat sb; + struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } expr->cost = STAT_COST; - expr->reftime = sb.st_mtim; - expr->time_field = field; + expr->reftime = sb.mtime; + expr->stat_field = field; return expr; fail: @@ -942,17 +936,9 @@ static struct expr *parse_time(struct parser_state *state, int field, int unit) return NULL; } -#if !BFS_HAVE_ST_BIRTHTIM - if (field == BTIME) { - cfprintf(state->cmdline->cerr, "%{er}error: %s: File birth times are not supported on this platform.%{rs}\n", expr->argv[0]); - free_expr(expr); - return NULL; - } -#endif - expr->cost = STAT_COST; expr->reftime = state->now; - expr->time_field = field; + expr->stat_field = field; expr->time_unit = unit; return expr; } @@ -1496,23 +1482,17 @@ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2 switch (arg[6]) { case 'a': - expr->time_field = ATIME; + expr->stat_field = BFS_STAT_ATIME; break; case 'c': - expr->time_field = CTIME; + expr->stat_field = BFS_STAT_CTIME; break; case 'm': - expr->time_field = MTIME; + expr->stat_field = BFS_STAT_MTIME; break; - case 'B': -#if BFS_HAVE_ST_BIRTHTIM - expr->time_field = BTIME; + expr->stat_field = BFS_STAT_BTIME; break; -#else - cfprintf(cerr, "%{er}error: %s: File birth times ('B') are not supported on this platform.%{rs}\n", arg); - goto fail; -#endif default: cfprintf(cerr, "%{er}error: %s: For -newerXY, X should be 'a', 'c', 'm', or 'B'.%{rs}\n", arg); @@ -1523,29 +1503,30 @@ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2 cfprintf(cerr, "%{er}error: %s: Explicit reference times ('t') are not supported.%{rs}\n", arg); goto fail; } else { - struct stat sb; + struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } switch (arg[7]) { case 'a': - expr->reftime = sb.st_atim; + expr->reftime = sb.atime; break; case 'c': - expr->reftime = sb.st_ctim; + expr->reftime = sb.ctime; break; case 'm': - expr->reftime = sb.st_mtim; + expr->reftime = sb.mtime; break; case 'B': -#if BFS_HAVE_ST_BIRTHTIM - expr->reftime = sb.st_birthtim; -#else - cfprintf(cerr, "%{er}error: %s: File birth times ('B') are not supported on this platform.%{rs}\n", arg); - goto fail; -#endif + if (sb.mask & BFS_STAT_BTIME) { + expr->reftime = sb.btime; + } else { + cfprintf(cerr, "%{er}error: '%s': Couldn't get file birth time.%{rs}\n", expr->sdata); + goto fail; + } + break; default: cfprintf(cerr, "%{er}error: %s: For -newerXY, Y should be 'a', 'c', 'm', 'B', or 't'.%{rs}\n", arg); @@ -2033,14 +2014,14 @@ static struct expr *parse_samefile(struct parser_state *state, int arg1, int arg return NULL; } - struct stat sb; + struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { free_expr(expr); return NULL; } - expr->dev = sb.st_dev; - expr->ino = sb.st_ino; + expr->dev = sb.dev; + expr->ino = sb.ino; expr->cost = STAT_COST; expr->probability = 0.01; @@ -2502,9 +2483,9 @@ struct table_entry { * The parse table for literals. */ static const struct table_entry parse_table[] = { - {"-Bmin", false, parse_time, BTIME, MINUTES}, - {"-Bnewer", false, parse_newer, BTIME}, - {"-Btime", false, parse_time, BTIME, DAYS}, + {"-Bmin", false, parse_time, BFS_STAT_BTIME, MINUTES}, + {"-Bnewer", false, parse_newer, BFS_STAT_BTIME}, + {"-Btime", false, parse_time, BFS_STAT_BTIME, DAYS}, {"-D", false, parse_debug}, {"-E", false, parse_regex_extended}, {"-O", true, parse_optlevel}, @@ -2513,13 +2494,13 @@ static const struct table_entry parse_table[] = { {"-L", false, parse_follow, BFTW_LOGICAL | BFTW_DETECT_CYCLES, false}, {"-X", false, parse_xargs_safe}, {"-a"}, - {"-amin", false, parse_time, ATIME, MINUTES}, + {"-amin", false, parse_time, BFS_STAT_ATIME, MINUTES}, {"-and"}, - {"-anewer", false, parse_newer, ATIME}, - {"-atime", false, parse_time, ATIME, DAYS}, - {"-cmin", false, parse_time, CTIME, MINUTES}, - {"-cnewer", false, parse_newer, CTIME}, - {"-ctime", false, parse_time, CTIME, DAYS}, + {"-anewer", false, parse_newer, BFS_STAT_ATIME}, + {"-atime", false, parse_time, BFS_STAT_ATIME, DAYS}, + {"-cmin", false, parse_time, BFS_STAT_CTIME, MINUTES}, + {"-cnewer", false, parse_newer, BFS_STAT_CTIME}, + {"-ctime", false, parse_time, BFS_STAT_CTIME, DAYS}, {"-color", false, parse_color, true}, {"-d", false, parse_depth}, {"-daystart", false, parse_daystart}, @@ -2554,12 +2535,12 @@ static const struct table_entry parse_table[] = { {"-ls", false, parse_ls}, {"-maxdepth", false, parse_depth_limit, false}, {"-mindepth", false, parse_depth_limit, true}, - {"-mmin", false, parse_time, MTIME, MINUTES}, - {"-mnewer", false, parse_newer, MTIME}, + {"-mmin", false, parse_time, BFS_STAT_MTIME, MINUTES}, + {"-mnewer", false, parse_newer, BFS_STAT_MTIME}, {"-mount", false, parse_mount}, - {"-mtime", false, parse_time, MTIME, DAYS}, + {"-mtime", false, parse_time, BFS_STAT_MTIME, DAYS}, {"-name", false, parse_name, false}, - {"-newer", false, parse_newer, MTIME}, + {"-newer", false, parse_newer, BFS_STAT_MTIME}, {"-newer", true, parse_newerxy}, {"-nocolor", false, parse_color, false}, {"-nogroup", false, parse_nogroup}, diff --git a/printf.c b/printf.c index 2d48cb6..27d9521 100644 --- a/printf.c +++ b/printf.c @@ -20,6 +20,7 @@ #include "dstring.h" #include "expr.h" #include "mtab.h" +#include "stat.h" #include "util.h" #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include typedef int bfs_printf_fn(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf); @@ -42,8 +42,8 @@ struct bfs_printf_directive { bfs_printf_fn *fn; /** String data associated with this directive. */ char *str; - /** The time field to print. */ - enum time_field time_field; + /** The stat field to print. */ + enum bfs_stat_field stat_field; /** Character data associated with this directive. */ char c; /** The current mount table. */ @@ -77,22 +77,22 @@ static int bfs_printf_flush(FILE *file, const struct bfs_printf_directive *direc (void)ret /** - * Get a particular time field from a struct stat. + * Get a particular time field from a struct bfs_stat. */ -static const struct timespec *get_time_field(const struct stat *statbuf, enum time_field time_field) { - switch (time_field) { - case ATIME: - return &statbuf->st_atim; - case CTIME: - return &statbuf->st_ctim; - case MTIME: - return &statbuf->st_mtim; - -#ifdef BFS_HAVE_ST_BIRTHTIM - case BTIME: - return &statbuf->st_birthtim; -#endif +static const struct timespec *get_time_field(const struct bfs_stat *statbuf, enum bfs_stat_field stat_field) { + if (!(statbuf->mask & stat_field)) { + return NULL; + } + switch (stat_field) { + case BFS_STAT_ATIME: + return &statbuf->atime; + case BFS_STAT_BTIME: + return &statbuf->btime; + case BFS_STAT_CTIME: + return &statbuf->ctime; + case BFS_STAT_MTIME: + return &statbuf->mtime; default: assert(false); return NULL; @@ -105,7 +105,11 @@ static int bfs_printf_ctime(FILE *file, const struct bfs_printf_directive *direc static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - const struct timespec *ts = get_time_field(ftwbuf->statbuf, directive->time_field); + const struct timespec *ts = get_time_field(ftwbuf->statbuf, directive->stat_field); + if (!ts) { + return -1; + } + struct tm tm; if (xlocaltime(&ts->tv_sec, &tm) != 0) { return -1; @@ -126,7 +130,11 @@ static int bfs_printf_ctime(FILE *file, const struct bfs_printf_directive *direc /** %A, %C, %T: strftime() */ static int bfs_printf_strftime(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - const struct timespec *ts = get_time_field(ftwbuf->statbuf, directive->time_field); + const struct timespec *ts = get_time_field(ftwbuf->statbuf, directive->stat_field); + if (!ts) { + return -1; + } + struct tm tm; if (xlocaltime(&ts->tv_sec, &tm) != 0) { return -1; @@ -175,7 +183,7 @@ static int bfs_printf_strftime(FILE *file, const struct bfs_printf_directive *di /** %b: blocks */ static int bfs_printf_b(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_blocks); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->blocks); return fprintf(file, directive->str, buf); } @@ -186,7 +194,7 @@ static int bfs_printf_d(FILE *file, const struct bfs_printf_directive *directive /** %D: device */ static int bfs_printf_D(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_dev); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->dev); return fprintf(file, directive->str, buf); } @@ -203,13 +211,13 @@ static int bfs_printf_F(FILE *file, const struct bfs_printf_directive *directive /** %G: gid */ static int bfs_printf_G(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_gid); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->gid); return fprintf(file, directive->str, buf); } /** %g: group name */ static int bfs_printf_g(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - struct group *grp = getgrgid(ftwbuf->statbuf->st_gid); + struct group *grp = getgrgid(ftwbuf->statbuf->gid); if (!grp) { return bfs_printf_G(file, directive, ftwbuf); } @@ -251,13 +259,13 @@ static int bfs_printf_H(FILE *file, const struct bfs_printf_directive *directive /** %i: inode */ static int bfs_printf_i(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_ino); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->ino); return fprintf(file, directive->str, buf); } /** %k: 1K blocks */ static int bfs_printf_k(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)(ftwbuf->statbuf->st_blocks + 1)/2); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)(ftwbuf->statbuf->blocks + 1)/2); return fprintf(file, directive->str, buf); } @@ -279,19 +287,19 @@ static int bfs_printf_l(FILE *file, const struct bfs_printf_directive *directive /** %m: mode */ static int bfs_printf_m(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - return fprintf(file, directive->str, (unsigned int)(ftwbuf->statbuf->st_mode & 07777)); + return fprintf(file, directive->str, (unsigned int)(ftwbuf->statbuf->mode & 07777)); } /** %M: symbolic mode */ static int bfs_printf_M(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { char buf[11]; - format_mode(ftwbuf->statbuf->st_mode, buf); + format_mode(ftwbuf->statbuf->mode, buf); return fprintf(file, directive->str, buf); } /** %n: link count */ static int bfs_printf_n(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_nlink); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->nlink); return fprintf(file, directive->str, buf); } @@ -311,25 +319,25 @@ static int bfs_printf_P(FILE *file, const struct bfs_printf_directive *directive /** %s: size */ static int bfs_printf_s(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_size); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->size); return fprintf(file, directive->str, buf); } /** %S: sparseness */ static int bfs_printf_S(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - double sparsity = 512.0 * ftwbuf->statbuf->st_blocks / ftwbuf->statbuf->st_size; + double sparsity = 512.0 * ftwbuf->statbuf->blocks / ftwbuf->statbuf->size; return fprintf(file, directive->str, sparsity); } /** %U: uid */ static int bfs_printf_U(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_uid); + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->uid); return fprintf(file, directive->str, buf); } /** %u: user name */ static int bfs_printf_u(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { - struct passwd *pwd = getpwuid(ftwbuf->statbuf->st_uid); + struct passwd *pwd = getpwuid(ftwbuf->statbuf->uid); if (!pwd) { return bfs_printf_U(file, directive, ftwbuf); } @@ -374,9 +382,9 @@ static int bfs_printf_Y(FILE *file, const struct bfs_printf_directive *directive const char *type = "U"; - struct stat sb; - if (fstatat(ftwbuf->at_fd, ftwbuf->at_path, &sb, 0) == 0) { - type = bfs_printf_type(mode_to_typeflag(sb.st_mode)); + struct bfs_stat sb; + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, 0, 0, &sb) == 0) { + type = bfs_printf_type(mode_to_typeflag(sb.mode)); } else { switch (errno) { case ELOOP: @@ -418,7 +426,7 @@ static struct bfs_printf_directive *new_directive() { perror("dstralloc()"); goto error; } - directive->time_field = 0; + directive->stat_field = 0; directive->c = 0; directive->mtab = NULL; directive->next = NULL; @@ -591,7 +599,7 @@ struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) switch (c) { case 'a': directive->fn = bfs_printf_ctime; - directive->time_field = ATIME; + directive->stat_field = BFS_STAT_ATIME; command->needs_stat = true; break; case 'b': @@ -600,7 +608,7 @@ struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) break; case 'c': directive->fn = bfs_printf_ctime; - directive->time_field = CTIME; + directive->stat_field = BFS_STAT_CTIME; command->needs_stat = true; break; case 'd': @@ -681,7 +689,7 @@ struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) break; case 't': directive->fn = bfs_printf_ctime; - directive->time_field = MTIME; + directive->stat_field = BFS_STAT_MTIME; command->needs_stat = true; break; case 'u': @@ -700,13 +708,13 @@ struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) break; case 'A': - directive->time_field = ATIME; + directive->stat_field = BFS_STAT_ATIME; goto directive_strftime; case 'C': - directive->time_field = CTIME; + directive->stat_field = BFS_STAT_CTIME; goto directive_strftime; case 'T': - directive->time_field = MTIME; + directive->stat_field = BFS_STAT_MTIME; goto directive_strftime; directive_strftime: diff --git a/printf.h b/printf.h index 6316c5d..353c82d 100644 --- a/printf.h +++ b/printf.h @@ -31,7 +31,7 @@ struct bfs_printf_directive; struct bfs_printf { /** The chain of printf directives. */ struct bfs_printf_directive *directives; - /** Whether the struct stat must be filled in. */ + /** Whether the struct bfs_stat must be filled in. */ bool needs_stat; }; diff --git a/stat.c b/stat.c new file mode 100644 index 0000000..4f1c2fc --- /dev/null +++ b/stat.c @@ -0,0 +1,243 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2018 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "stat.h" +#include "util.h" +#include +#include +#include +#include +#include +#include + +#if __linux__ +# include +# include +# include +#elif __APPLE__ +# define st_atim st_atimespec +# define st_ctim st_ctimespec +# define st_mtim st_mtimespec +# define st_birthtim st_birthtimespec +#endif + +/** + * Check if we should retry a failed stat() due to a potentially broken link. + */ +static bool bfs_stat_retry(int ret, int at_flags, enum bfs_stat_flag flags) { + return ret != 0 + && !(at_flags & AT_SYMLINK_NOFOLLOW) + && (flags & BFS_STAT_BROKEN_OK) + && is_nonexistence_error(errno); +} + +/** + * Convert a struct stat to a struct bfs_stat. + */ +static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) { + buf->mask = 0; + + buf->dev = statbuf->st_dev; + buf->mask |= BFS_STAT_DEV; + + buf->ino = statbuf->st_ino; + buf->mask |= BFS_STAT_INO; + + buf->mode = statbuf->st_mode; + buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; + + buf->nlink = statbuf->st_nlink; + buf->mask |= BFS_STAT_NLINK; + + buf->gid = statbuf->st_gid; + buf->mask |= BFS_STAT_GID; + + buf->uid = statbuf->st_uid; + buf->mask |= BFS_STAT_UID; + + buf->size = statbuf->st_size; + buf->mask |= BFS_STAT_SIZE; + + buf->blocks = statbuf->st_blocks; + buf->mask |= BFS_STAT_BLOCKS; + + buf->atime = statbuf->st_atim; + buf->mask |= BFS_STAT_ATIME; + + buf->ctime = statbuf->st_ctim; + buf->mask |= BFS_STAT_CTIME; + + buf->mtime = statbuf->st_mtim; + buf->mask |= BFS_STAT_MTIME; + +#if __APPLE__ || __FreeBSD__ || __NetBSD__ + buf->btime = statbuf->st_birthtim; + buf->mask |= BFS_STAT_BTIME; +#endif +} + +/** + * bfs_stat() implementation backed by stat(). + */ +static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { + struct stat statbuf; + int ret = fstatat(at_fd, at_path, &statbuf, at_flags); + + if (bfs_stat_retry(ret, at_flags, flags)) { + at_flags |= AT_SYMLINK_NOFOLLOW; + ret = fstatat(at_fd, at_path, &statbuf, at_flags); + } + + if (ret == 0) { + bfs_stat_convert(&statbuf, buf); + } + + return ret; +} + +#ifdef STATX_BASIC_STATS + +/** + * Wrapper for the statx() system call, which has no native glibc wrapper. + */ +static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { + return syscall(__NR_statx, at_fd, at_path, at_flags, mask, buf); +} + +/** + * bfs_stat() implementation backed by statx(). + */ +static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + struct statx xbuf; + int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); + + if (bfs_stat_retry(ret, at_flags, flags)) { + at_flags |= AT_SYMLINK_NOFOLLOW; + ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); + } + + if (ret != 0) { + return ret; + } + + if ((xbuf.stx_mask & STATX_BASIC_STATS) != STATX_BASIC_STATS) { + // Callers shouldn't have to check anything except BFS_STAT_BTIME + errno = EINVAL; + return -1; + } + + buf->mask = 0; + + buf->dev = makedev(xbuf.stx_dev_major, xbuf.stx_dev_minor); + buf->mask |= BFS_STAT_DEV; + + if (xbuf.stx_mask & STATX_INO) { + buf->ino = xbuf.stx_ino; + buf->mask |= BFS_STAT_INO; + } + + buf->mode = xbuf.stx_mode; + if (xbuf.stx_mask & STATX_TYPE) { + buf->mask |= BFS_STAT_TYPE; + } + if (xbuf.stx_mask & STATX_MODE) { + buf->mask |= BFS_STAT_MODE; + } + + if (xbuf.stx_mask & STATX_NLINK) { + buf->nlink = xbuf.stx_nlink; + buf->mask |= BFS_STAT_NLINK; + } + + if (xbuf.stx_mask & STATX_GID) { + buf->gid = xbuf.stx_gid; + buf->mask |= BFS_STAT_GID; + } + + if (xbuf.stx_mask & STATX_UID) { + buf->uid = xbuf.stx_uid; + buf->mask |= BFS_STAT_UID; + } + + if (xbuf.stx_mask & STATX_SIZE) { + buf->size = xbuf.stx_size; + buf->mask |= BFS_STAT_SIZE; + } + + if (xbuf.stx_mask & STATX_BLOCKS) { + buf->blocks = xbuf.stx_blocks; + buf->mask |= BFS_STAT_BLOCKS; + } + + if (xbuf.stx_mask & STATX_ATIME) { + buf->atime.tv_sec = xbuf.stx_atime.tv_sec; + buf->atime.tv_nsec = xbuf.stx_atime.tv_nsec; + buf->mask |= BFS_STAT_ATIME; + } + + if (xbuf.stx_mask & STATX_BTIME) { + buf->btime.tv_sec = xbuf.stx_btime.tv_sec; + buf->btime.tv_nsec = xbuf.stx_btime.tv_nsec; + buf->mask |= BFS_STAT_BTIME; + } + + if (xbuf.stx_mask & STATX_CTIME) { + buf->ctime.tv_sec = xbuf.stx_ctime.tv_sec; + buf->ctime.tv_nsec = xbuf.stx_ctime.tv_nsec; + buf->mask |= BFS_STAT_CTIME; + } + + if (xbuf.stx_mask & STATX_MTIME) { + buf->mtime.tv_sec = xbuf.stx_mtime.tv_sec; + buf->mtime.tv_nsec = xbuf.stx_mtime.tv_nsec; + buf->mask |= BFS_STAT_MTIME; + } + + return ret; +} + +#endif // STATX_BASIC_STATS + +int bfs_stat(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { +#ifdef STATX_BASIC_STATS + static bool has_statx = true; + + if (has_statx) { + int ret = bfs_statx_impl(at_fd, at_path, at_flags, flags, buf); + if (ret != 0 && errno == ENOSYS) { + has_statx = false; + } else { + return ret; + } + } +#endif + + return bfs_stat_impl(at_fd, at_path, at_flags, flags, buf); +} + +int bfs_fstat(int fd, struct bfs_stat *buf) { +#ifdef AT_EMPTY_PATH + return bfs_stat(fd, "", AT_EMPTY_PATH, 0, buf); +#else + struct stat statbuf; + int ret = fstat(fd, &statbuf); + if (ret == 0) { + bfs_stat_convert(&statbuf, buf); + } + return ret; +#endif +} diff --git a/stat.h b/stat.h new file mode 100644 index 0000000..d47d248 --- /dev/null +++ b/stat.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2018 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#ifndef BFS_STAT_H +#define BFS_STAT_H + +#include +#include + +/** + * bfs_stat field bitmask. + */ +enum bfs_stat_field { + BFS_STAT_DEV = 1 << 0, + BFS_STAT_INO = 1 << 1, + BFS_STAT_TYPE = 1 << 2, + BFS_STAT_MODE = 1 << 3, + BFS_STAT_NLINK = 1 << 4, + BFS_STAT_GID = 1 << 5, + BFS_STAT_UID = 1 << 6, + BFS_STAT_SIZE = 1 << 7, + BFS_STAT_BLOCKS = 1 << 8, + BFS_STAT_ATIME = 1 << 9, + BFS_STAT_BTIME = 1 << 10, + BFS_STAT_CTIME = 1 << 11, + BFS_STAT_MTIME = 1 << 12, +}; + +/** + * bfs_stat() flags. + */ +enum bfs_stat_flag { + /** Fall back to the link itself on broken symlinks. */ + BFS_STAT_BROKEN_OK = 1 << 0, +}; + +/** + * Facade over struct stat. + */ +struct bfs_stat { + /** Bitmask indicating filled fields. */ + enum bfs_stat_field mask; + + /** Device ID containing the file. */ + dev_t dev; + /** Inode number. */ + ino_t ino; + /** File type and access mode. */ + mode_t mode; + /** Number of hard links. */ + nlink_t nlink; + /** Owner group ID. */ + gid_t gid; + /** Owner user ID. */ + uid_t uid; + /** File size in bytes. */ + off_t size; + /** Number of 512B blocks allocated. */ + blkcnt_t blocks; + + /** Access time. */ + struct timespec atime; + /** Birth/creation time. */ + struct timespec btime; + /** Status change time. */ + struct timespec ctime; + /** Modification time. */ + struct timespec mtime; +}; + +/** + * Facade over fstatat(). + */ +int bfs_stat(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf); + +/** + * Facade over fstat(). + */ +int bfs_fstat(int fd, struct bfs_stat *buf); + +#endif // BFS_STAT_H diff --git a/util.c b/util.c index 79bb2d2..9182a80 100644 --- a/util.c +++ b/util.c @@ -262,17 +262,6 @@ bool is_nonexistence_error(int error) { return error == ENOENT || errno == ENOTDIR; } -int xfstatat(int fd, const char *path, struct stat *buf, int flags) { - int ret = fstatat(fd, path, buf, flags); - - if (ret != 0 && !(flags & AT_SYMLINK_NOFOLLOW) && is_nonexistence_error(errno)) { - flags |= AT_SYMLINK_NOFOLLOW; - ret = fstatat(fd, path, buf, flags); - } - - return ret; -} - enum bftw_typeflag mode_to_typeflag(mode_t mode) { switch (mode & S_IFMT) { #ifdef S_IFBLK diff --git a/util.h b/util.h index efeea19..81caf12 100644 --- a/util.h +++ b/util.h @@ -23,22 +23,11 @@ #include #include #include -#include +#include #include // Some portability concerns -#if __APPLE__ -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -# define st_birthtim st_birthtimespec -#endif - -#if __APPLE__ || __FreeBSD__ || __NetBSD__ -# define BFS_HAVE_ST_BIRTHTIM true -#endif - #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) # define FNM_CASEFOLD FNM_IGNORECASE #endif @@ -167,22 +156,7 @@ int xfaccessat(int fd, const char *path, int amode); bool is_nonexistence_error(int error); /** - * stat() a file, falling back on the link itself for broken symbolic links. - * - * @param fd - * The base directory descriptor. - * @param path - * The path to the file, relative to fd. - * @param buf - * The stat buffer to fill. - * @param flags - * AT_* flags for this call. - * @return 0 on success, -1 on failure. - */ -int xfstatat(int fd, const char *path, struct stat *buf, int flags); - -/** - * Convert a stat() st_mode to a bftw() typeflag. + * Convert a bfs_stat() mode to a bftw() typeflag. */ enum bftw_typeflag mode_to_typeflag(mode_t mode); -- cgit v1.2.3