1 #include <stdexcept>
2 #include <algorithm>
3 #include "varexp.h"
4 #include "auto_dir.h"
5 #if defined(_WIN32)
6 # include <io.h>
7 # define F_OK 0
8 # define X_OK 0
9 #else
10 # include <unistd.h>
11 #endif
12 #include "fileexp.h"
13
14 /*
15
16 copyright (c) 2004, 2005, 2015 squell <squell@alumina.nl>
17
18 use, modification, copying and distribution of this software is permitted
19 under the conditions described in the file 'COPYING'.
20
21 */
22
23 #if defined(__DJGPP__) || defined(_WIN32)
24 # define readlink(path, buf, n) (-1) // readlink dummy
25 #endif
26
27 using namespace std;
28
29 namespace fileexp {
30
31 /* filefind::nested variables split in two categories: stuff that drives the
32 the recursion (parameters); and supporting information (class members) */
33
34 struct filefind : record {
35 char mask[PATH_MAX];
36 find* invoker;
37 char* rec_base; // start of recursive search
38
39 class direxp;
40 bool nested (auto_dir, char* pathpos, char* filespec);
41 char* pathcpy(char*, const char*); // special strcpy variant
42 };
43
glob(const char * filemask,bool wildslash)44 bool find::glob(const char* filemask, bool wildslash)
45 {
46 filefind t;
47 strncpy(t.mask, filemask, sizeof t.mask);
48 t.path[0] = t.mask[sizeof t.mask-1] = '\0'; // duct tape
49 t.invoker = this;
50 t.rec_base = wildslash? t.path : 0;
51 #if defined(_WIN32)
52 if(strncmp(t.mask, "//", 2) == 0) { // unc path
53 char* shmask = strchr(t.mask+2, '/');
54 if(shmask && (shmask=strchr(++shmask, '/'))) {
55 *shmask++ = '\0'; // separate sharename
56 char* shpath = t.pathcpy(t.pathcpy(t.path, t.mask), "/");
57 if(t.rec_base) t.rec_base = shpath;
58 return t.nested(auto_dir(t.mask), shpath, shmask);
59 }
60 return false;
61 } else
62 #endif
63 return t.nested(auto_dir("./"), t.path, t.mask);
64 }
65
66 struct filefind::direxp : varexp { // special dotfile handling
is_specialfileexp::filefind::direxp67 inline static bool is_special(const char* fn)
68 {
69 return (fn[0] == '.') && (!fn[1] || fn[1] == '.' && !fn[2]);
70 }
71
direxpfileexp::filefind::direxp72 direxp(const char* mask, const char* test, bool force = false)
73 {
74 #if defined(__DJGPP__) || defined(_WIN32)
75 if(force || !is_special(test))
76 result = match(mask, test);
77 else
78 result = 0;
79 #else // conform to Bourne shell
80 if(force || test[0] != '.' || mask[0] == '.')
81 result = match(mask, test);
82 else
83 result = 0;
84 #endif
85 }
86 };
87
pathcpy(char * dest,const char * src)88 char* filefind::pathcpy(char* dest, const char* src)
89 {
90 do { // added safety
91 if(dest == &path[sizeof path])
92 throw length_error("filefindexp: pathname error");
93 } while(*dest++ = *src++);
94 return dest-1; // return *resume*-ptr
95 }
96
97 struct slash_split {
slash_splitfileexp::slash_split98 slash_split(char* p) : ptr(p) { *ptr = '\0'; }
~slash_splitfileexp::slash_split99 ~slash_split() { *ptr = '/'; }
100 private:
101 char* ptr;
102 };
103
104 // recursive file search routine, leaves this->mask nonsensical on success
105 // pathpos - write position (inside path)
106 // filespec - read position (inside mask)
107
nested(auto_dir dir,char * pathpos,char * filespec)108 bool filefind::nested(auto_dir dir, char* pathpos, char* filespec)
109 {
110 typedef vector<string> strvec;
111 strvec::size_type prevlen = var.size(); // backup value
112 char* wpos; // next write position
113
114 bool w = false; // idle check
115
116 if(!rec_base || rec_base == pathpos)
117 while( char* fndirsep = strchr(filespec, '/') ) {
118 slash_split lock(fndirsep++);
119 wpos = pathcpy(pathcpy(pathpos, filespec), "/");
120 if(access(path, X_OK) == 0) {
121 dir = auto_dir(path); // if allready a valid
122 pathpos = wpos; // directory, use as is
123 filespec = fndirsep; // (tail recursion)
124 if(rec_base) rec_base = pathpos;
125 } else if(!strpbrk(filespec, "*?[") || !dir) {
126 return false; // shortcut mismatchers
127 } else if(rec_base && strchr(filespec,'*')) {
128 break;
129 } else {
130 while( dirent* fn = dir.read() ) { // search cur open dir
131 direxp match(filespec, fn->d_name);
132 if(match) {
133 wpos = pathcpy(pathcpy(pathpos, fn->d_name), "/");
134 if(rec_base) rec_base = wpos;
135 if(auto_dir newdir = auto_dir(path)) {
136 for(varexp::iterator i = match.begin(); i != match.end(); ++i)
137 var.push_back(*i);
138 w |= nested(newdir, wpos, fndirsep);
139 var.resize(prevlen);
140 }
141 }
142 }
143 return w;
144 }
145 }
146
147 if(! invoker->dir(*this) )
148 return false;
149
150 if(*filespec == '\0')
151 return invoker->file(pathpos, *this);
152
153 pathcpy(pathpos, filespec); // check if file is 'simple'
154 if(!strpbrk(filespec, "*?["))
155 return access(path, F_OK) == 0 && invoker->file(pathpos, *this);
156
157 if(! dir ) // we might not have read access
158 return false;
159
160 strvec files;
161 while( dirent* fn = dir.read() ) { // read all files in dir
162 if(!direxp::is_special(fn->d_name))
163 files.push_back(fn->d_name);
164 }
165
166 sort(files.begin(), files.end()); // speeds things up!? wow
167
168 for(strvec::iterator fn = files.begin(); fn != files.end(); ++fn) {
169 wpos = pathcpy(pathpos, fn->c_str());
170 direxp match(filespec, rec_base? rec_base : pathpos, !!rec_base);
171 if(match) {
172 for(varexp::iterator i = match.begin(); i != match.end(); ++i)
173 var.push_back(*i);
174 w |= invoker->file(pathpos, *this);
175 var.resize(prevlen);
176 }
177 }
178
179 if(rec_base)
180 for(strvec::iterator fn = files.begin(); fn != files.end(); ++fn) {
181 wpos = pathcpy(pathpos, fn->c_str());
182 auto_dir newdir(path);
183 char sympath[1]; // dont recurse into symlinks
184 if(newdir && readlink(path, sympath, sizeof sympath) < 0)
185 w |= nested(newdir, pathcpy(wpos, "/"), filespec);
186 }
187
188 return w;
189 }
190
191 } // namespace
192