Skip to content

Commit a3eee6e

Browse files
committed
replace glob impl with fts
1 parent f5d32a4 commit a3eee6e

File tree

8 files changed

+84
-126
lines changed

8 files changed

+84
-126
lines changed

include/deng.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct index_s;
1111
/// @enum deng_notif_t
1212
/// @brief Notification events for the file search process
1313
enum deng_notif_t {
14-
DENG_NOTIF_DIR_DONE, ///< Occurs when a directory has been fully processed
14+
DENG_NOTIF_FILE_FOUND,///< Occurs when a file will be processed
1515
DENG_NOTIF_STAGE_DONE,///< Occurs when a stage has been fully processed
1616
};
1717

include/fs.h

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,35 @@
33
#ifndef FSAUTOPROC_FS_H
44
#define FSAUTOPROC_FS_H
55

6-
#include <stdbool.h>
76
#include <stdint.h>
87

8+
/// @struct fsstat_s
9+
/// @brief Stat structure for storing the last modified time and file size.
10+
struct fsstat_s {
11+
uint64_t lmod;///< Last modified time in milliseconds since epoch
12+
uint64_t fsze;///< File size in bytes
13+
};
14+
915
/// @typedef fswalkfn_t
1016
/// @brief Callback function used by `fswalk` to process files and directories.
1117
/// @param fp The file or directory path to process
18+
/// @param st File stat information
1219
/// @param udata User data passed to the callback function
1320
/// @return 0 if the walk should continue, otherwise a non-zero value to stop
14-
typedef int (*fswalkfn_t)(const char* fp, void* udata);
21+
typedef int (*fswalkfn_t)(const char* fp, const struct fsstat_s* st,
22+
void* udata);
1523

1624
/// @brief Recursively walks the single directory described by \p dir and calls
17-
/// \p filefn for each file found and \p dirfn for each directory encountered.
25+
/// \p filefn for each file found.
1826
/// @param dir The directory to walk
1927
/// @param filefn The function to call for each file found. The filepath is
2028
/// passed as the first argument. If the function returns a non-zero value, the
2129
/// walk is terminated and the same value is returned by `fswalk`.
22-
/// @param dirfn The function to call for each directory found. The directory
23-
/// path is passed as the first argument. If the function returns a non-zero
24-
/// value, the walk is terminated and the same value is returned by `fswalk`.
2530
/// @param udata User data to pass to \p filefn and \p dirfn
2631
/// @return If successful, 0 is returned. An internal `fswalk` error will return
2732
/// a value of -1 and `errno` is set. Otherwise the return value of the first
2833
/// non-zero \p filefn or \p dirfn call is returned.
29-
int fswalk(const char* dir, fswalkfn_t filefn, fswalkfn_t dirfn, void* udata);
30-
31-
/// @struct fsstat_s
32-
/// @brief Stat structure for storing the last modified time and file size.
33-
struct fsstat_s {
34-
uint64_t lmod; ///< Last modified time in milliseconds since epoch
35-
uint64_t fsze; ///< File size in bytes
36-
};
37-
38-
/// @brief `fsstateql()` compares the fields of two `struct fsstat_s` values for
39-
/// equality. Two structs with the same values are considered equal.
40-
/// @param a The first `struct fsstat_s` to compare
41-
/// @param b The second `struct fsstat_s` to compare
42-
/// @return Returns true if the two `struct fsstat_s` values are equal.
43-
bool fsstateql(const struct fsstat_s* a, const struct fsstat_s* b);
34+
int fswalk(const char* dir, fswalkfn_t filefn, void* udata);
4435

4536
/// @brief Populates all fields of a given \p fsstat_s structure for the file
4637
/// described by the filepath \p fp.

include/index.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#ifndef FSAUTOPROC_INDEX_H
44
#define FSAUTOPROC_INDEX_H
55

6+
#include <stdint.h>
67
#include <stdio.h>
78

89
#include "fs.h"
@@ -80,7 +81,7 @@ int indexread(struct index_s* idx, FILE* s);
8081
/// @return The pointer to the new node in the index map, otherwise NULL is
8182
/// returned and `errno` is set.
8283
struct inode_s* indexput(struct index_s* idx, const char* fp, uint64_t fphash,
83-
struct fsstat_s st);
84+
const struct fsstat_s* st);
8485

8586
/// @brief Frees all nodes in the index map.
8687
/// @param idx The index to free

src/deng.c

Lines changed: 19 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,12 @@
99

1010
#include "fs.h"
1111
#include "index.h"
12-
#include "je.h"
1312
#include "log.h"
1413

15-
#define SL_OVERRIDE
16-
#define SLX_REALLOC je_realloc
17-
#define SLX_FREE je_free
18-
#define SLX_STRDUP je_strdup
19-
20-
#define SL_IMPL
21-
#include "sl.h"
22-
2314
/// @struct deng_state_s
2415
/// @brief Search state context provided to the diff engine as user data which
2516
/// is passed to the file event hook functions.
2617
struct deng_state_s {
27-
slist_t dirqueue; ///< Processing directory queue
2818
deng_filter_t ffn; ///< File filter function
2919
const struct deng_hooks_s* hooks;///< File event hook functions
3020
const struct index_s* lastmap; ///< Previous index state
@@ -54,14 +44,14 @@ struct deng_state_s {
5444
/// files are indexed. This function may trigger new (NEW), modified (MOD),
5545
/// and unmodified (NOP) events for each file in the directory tree.
5646
/// @param fp The file path to process
47+
/// @param st The file stat information
5748
/// @param udata The diff engine state context
5849
/// @return 0 if successful, otherwise a non-zero error code.
59-
static int stagepre(const char* fp, void* udata) {
50+
static int stagepre(const char* fp, const struct fsstat_s* st, void* udata) {
6051
struct deng_state_s* mach = (struct deng_state_s*) udata;
61-
if (mach->ffn != NULL && mach->ffn(fp)) return 0;
52+
notifyhook(mach, DENG_NOTIF_FILE_FOUND);
6253

63-
struct fsstat_s st = {0};
64-
if (fsstat(fp, &st)) return -1;
54+
if (mach->ffn != NULL && mach->ffn(fp)) return 0;// skip filtered files
6555

6656
const uint64_t fphash = indexhash(fp);
6757

@@ -74,9 +64,8 @@ static int stagepre(const char* fp, void* udata) {
7464
if ((curr = indexput(mach->thismap, fp, fphash, st)) == NULL) return -1;
7565

7666
if (prev != NULL) {
77-
if (!fsstateql(&prev->st, &curr->st)) {
67+
if (prev->st.lmod != st->lmod || prev->st.fsze != st->fsze)
7868
callevent(mach, DENG_FEVENT_MOD, curr);
79-
}
8069
} else {
8170
callevent(mach, DENG_FEVENT_NEW, curr);
8271
}
@@ -88,67 +77,44 @@ static int stagepre(const char* fp, void* udata) {
8877
/// files are indexed. This function may trigger new (NEW) and modified (MOD)
8978
/// events for each file in the directory tree.
9079
/// @param fp The file path to process
80+
/// @param st The file stat information
9181
/// @param udata The diff engine state context
9282
/// @return 0 if successful, otherwise a non-zero error code.
93-
static int stagepost(const char* fp, void* udata) {
83+
static int stagepost(const char* fp, const struct fsstat_s* st, void* udata) {
9484
struct deng_state_s* mach = (struct deng_state_s*) udata;
95-
if (mach->ffn != NULL && mach->ffn(fp)) return 0;
85+
notifyhook(mach, DENG_NOTIF_FILE_FOUND);
86+
87+
if (mach->ffn != NULL && mach->ffn(fp)) return 0;// skip filtered files
9688

9789
const uint64_t fphash = indexhash(fp);
9890

9991
struct inode_s* curr = indexfind(mach->thismap, fp, fphash);
10092
if (curr != NULL) {
101-
// check if the file was modified during the command execution
102-
struct fsstat_s mod = {0};
103-
if (fsstat(fp, &mod)) return -1;
104-
curr->st = mod;// update the file info in the current index
93+
curr->st = *st;// update the file info in the current index
10594
return 0;
10695
}
10796

108-
struct fsstat_s st = {0};
109-
if (fsstat(fp, &st)) return -1;
11097
if ((curr = indexput(mach->thismap, fp, fphash, st)) == NULL) return -1;
11198
callevent(mach, DENG_FEVENT_NEW, curr);
11299

113100
return 0;
114101
}
115102

116-
/// @brief Pushes a directory path onto the directory queue for processing.
117-
/// @param fp The directory path to push
118-
/// @param udata The diff engine state context
119-
/// @return 0 if successful, otherwise a non-zero error code.
120-
static int dqpush(const char* fp, void* udata) {
121-
struct deng_state_s* mach = (struct deng_state_s*) udata;
122-
int err;
123-
if ((err = sladd(&mach->dirqueue, fp)))
124-
log_error("error pushing directory `%s`", fp);
125-
return err;
126-
}
127-
128-
/// @brief Resets the directory queue to the initial search path, and invokes
129-
/// the `filefn` function for each file in the directory tree, recursively.
103+
/// @brief Walks the directory tree starting at \p sd and invokes the provided
104+
/// file function \p filefn for each file found. After the walk is complete,
105+
/// the stage done notification is triggered.
130106
/// @param mach The diff engine state context
131107
/// @param sd The initial search directory path
132108
/// @param filefn The function to invoke for each file in the directory tree
133109
/// @return 0 if successful, otherwise a non-zero error code.
134110
static int execstage(struct deng_state_s* mach, const char* sd,
135111
fswalkfn_t filefn) {
136-
slfree(&mach->dirqueue);
137-
mach->dirqueue = (slist_t){0};
138-
if (sladd(&mach->dirqueue, sd)) return -1;
139-
140-
char* dir;
141-
while ((dir = slpop(&mach->dirqueue)) != NULL) {
142-
int err;
143-
if ((err = fswalk(dir, filefn, dqpush, (void*) mach))) {
144-
log_error("file func for `%s` returned %d", dir, err);
145-
return -1;
146-
}
147-
notifyhook(mach, DENG_NOTIF_DIR_DONE);
148-
je_free(dir);
112+
int err;
113+
if ((err = fswalk(sd, filefn, (void*) mach))) {
114+
log_error("file func for `%s` returned %d", sd, err);
115+
return -1;
149116
}
150117
notifyhook(mach, DENG_NOTIF_STAGE_DONE);
151-
152118
return 0;
153119
}
154120

@@ -180,12 +146,11 @@ int dengsearch(const char* sd, deng_filter_t filter,
180146
assert(old != NULL);
181147
assert(new != NULL);
182148

183-
struct deng_state_s mach = {{0}, filter, hooks, old, new};
149+
struct deng_state_s mach = {filter, hooks, old, new};
184150
int err;
185151
if ((err = execstage(&mach, sd, stagepre))) goto ret;
186152
if ((err = checkremoved(&mach))) goto ret;
187153
if ((err = execstage(&mach, sd, stagepost))) goto ret;
188154
ret:
189-
slfree(&mach.dirqueue);
190155
return err;
191156
}

src/fs.c

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
#include "fs.h"
44

55
#include <assert.h>
6-
#include <glob.h>
6+
#include <errno.h>
7+
#include <fts.h>
78
#include <stdio.h>
89
#include <string.h>
910
#include <sys/stat.h>
@@ -13,59 +14,51 @@
1314

1415
#include "log.h"
1516

16-
/// @brief Error callback implementation for glob search. Logs each error using
17-
/// the \p log_error macro.
18-
/// @param epath The path that caused the error.
19-
/// @param errno The error number.
20-
/// @return Always returns 0 to continue the glob scan.
21-
static int fswalkerr(const char* epath, const int errno) {
22-
log_error("error accessing `%s`: %d", epath, errno);
23-
return 0; /* continue glob scan */
24-
}
25-
26-
int fswalk(const char* dir, fswalkfn_t filefn, fswalkfn_t dirfn, void* udata) {
27-
char pattern[256]; /* glob pattern format buffer */
28-
glob_t gres = {0}; /* glob search results */
29-
int ret; /* glob return value */
30-
31-
// ensure pattern has capacity for dir + wildcard suffix
32-
assert(strlen(dir) < sizeof(pattern));
33-
snprintf(pattern, sizeof(pattern), "%s/*", dir);
34-
35-
if ((ret = glob(pattern, GLOB_MARK | GLOB_TILDE, fswalkerr, &gres))) {
36-
globfree(&gres);
37-
if (ret == GLOB_NOMATCH) return 0;
38-
return -1;
39-
}
40-
17+
/// @brief Internal helper function to process a single file entry from the FTS
18+
/// walk. The file's last modified time and size are extracted from the stat
19+
/// structure and passed to the user-defined callback function.
20+
/// @param ent The FTSENT file entry to process
21+
/// @param filefn The user-defined callback function to invoke for the file
22+
/// @param udata User data pointer to pass to the callback function
23+
/// @return The return value of the user-defined callback function
24+
static int fsprocent(FTSENT* ent, fswalkfn_t filefn, void* udata) {
4125
#if defined(__FreeBSD__) || defined(__APPLE__)
42-
const int mc = gres.gl_matchc;
26+
const struct timespec ts = ent->fts_statp->st_mtimespec; /* last modified */
4327
#else
44-
const int mc = gres.gl_pathc;
28+
const struct timespec ts = ent->fts_statp->st_mtim; /* last modified */
4529
#endif
30+
struct fsstat_s st = {0};
31+
st.lmod = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; /* convert to millis */
32+
st.fsze = ent->fts_statp->st_size; /* copy file size */
33+
return filefn(ent->fts_path, &st, udata);
34+
}
4635

47-
// invoke process function for each result
36+
int fswalk(const char* dir, fswalkfn_t filefn, void* udata) {
37+
char* const paths[] = {(char*) dir, NULL};
38+
FTS* ftsp = fts_open(paths, FTS_LOGICAL, NULL);
39+
if (ftsp == NULL) {
40+
log_error("fts_open error on `%s`: %s", dir, strerror(errno));
41+
return -1;
42+
}
4843
int err = 0;
49-
for (int i = 0; i < mc; i++) {
50-
char* fp = gres.gl_pathv[i];
51-
const size_t len = strlen(fp);
52-
if (fp[len - 1] == '/') {// if directory, invoke directory function
53-
fp[len - 1] = '\0'; // remove the trailing slash generated by GLOB_MARK
54-
if ((err = dirfn(fp, udata))) goto doret;
55-
} else if ((err = filefn(gres.gl_pathv[i], udata))) {
56-
goto doret;
44+
FTSENT* ent;
45+
while ((ent = fts_read(ftsp)) != NULL) {
46+
if (ent->fts_info == FTS_ERR) {// handle error entries
47+
log_error("fts_read error on `%s`: %s", ent->fts_path,
48+
strerror(ent->fts_errno));
49+
} else if (ent->fts_info == FTS_F) {
50+
if ((err = fsprocent(ent, filefn, udata))) goto doret;
5751
}
5852
}
59-
53+
if (errno) {// fts_read will set errno if an error occurred
54+
log_error("fts_read error on `%s`: %s", dir, strerror(errno));
55+
err = -1;
56+
}
6057
doret:
61-
globfree(&gres);
58+
fts_close(ftsp);
6259
return err;
6360
}
6461

65-
bool fsstateql(const struct fsstat_s* a, const struct fsstat_s* b) {
66-
return a->lmod == b->lmod && a->fsze == b->fsze;
67-
}
68-
6962
int fsstat(const char* fp, struct fsstat_s* s) {
7063
struct stat st = {0};
7164
if (stat(fp, &st)) return -1;

src/index.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ int indexread(struct index_s* idx, FILE* s) {
8181
while (fscanf(s, "%[^,],%" PRIu64 ",%" PRIu64 "\n", fp, &st.lmod, &st.fsze) ==
8282
3) {
8383
const uint64_t fphash = indexhash(fp);
84-
if (indexput(idx, fp, fphash, st) == NULL) return -1;
84+
if (indexput(idx, fp, fphash, &st) == NULL) return -1;
8585
}
8686
return 0;
8787
}
@@ -101,14 +101,14 @@ static void indexappend(struct ibucket_s* bucket, struct inode_s* node) {
101101
}
102102

103103
struct inode_s* indexput(struct index_s* idx, const char* fp,
104-
const uint64_t fphash, const struct fsstat_s st) {
104+
const uint64_t fphash, const struct fsstat_s* st) {
105105
struct inode_s* node = je_malloc(sizeof(struct inode_s));
106106
if (node == NULL) return NULL;
107107
if ((fp = je_strdup(fp)) == NULL) {// duplicate filepath string
108108
je_free(node);
109109
return NULL;
110110
}
111-
*node = (struct inode_s) {(char*) fp, fphash, st, NULL};
111+
*node = (struct inode_s) {(char*) fp, fphash, *st, NULL};
112112
struct ibucket_s* bucket = &idx->buckets[indexbucket(node->fphash)];
113113
indexappend(bucket, node);
114114
idx->size++;

src/lcmd.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,16 @@
1818
#include "fd.h"
1919
#include "je.h"
2020
#include "log.h"
21-
#include "sl.h"
2221
#include "tm.h"
2322

23+
#define SL_OVERRIDE
24+
#define SLX_REALLOC je_realloc
25+
#define SLX_FREE je_free
26+
#define SLX_STRDUP je_strdup
27+
28+
#define SL_IMPL
29+
#include "sl.h"
30+
2431
/// @brief Frees the memory allocated for a single command set entry struct.
2532
/// @param cmd Command set entry to free
2633
static void lcmdfree(struct lcmdset_s* cmd) {

src/main.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,8 @@ static bool filterjunk(const char* fp) {
251251
if (junk) {
252252
if (initargs.verbose) log_info("[j] %s", fp);
253253
} else {
254-
indexput(&goodmap, fp, fphash, (struct fsstat_s) {0});// mark as known good
254+
const struct fsstat_s st = {0};
255+
indexput(&goodmap, fp, fphash, &st);// mark as known good
255256
}
256257
return junk;
257258
}
@@ -263,7 +264,7 @@ static bool filterjunk(const char* fp) {
263264
/// @param notif The notification type
264265
static void onnotify(const enum deng_notif_t notif) {
265266
switch (notif) {
266-
case DENG_NOTIF_DIR_DONE:
267+
case DENG_NOTIF_FILE_FOUND:
267268
printprogbar(thismap.size, lastmap.size);
268269
break;
269270
case DENG_NOTIF_STAGE_DONE:

0 commit comments

Comments
 (0)