1 /**
2 * Copyright (c) 2017, Timothy Stack
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of Timothy Stack nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <glob.h>
35 #include <grp.h>
36 #include <pwd.h>
37
38 #include "base/injector.hh"
39 #include "base/lnav_log.hh"
40 #include "sql_util.hh"
41 #include "vtab_module.hh"
42 #include "fstat_vtab.hh"
43
44 using namespace std;
45
46 enum {
47 FSTAT_COL_PARENT,
48 FSTAT_COL_NAME,
49 FSTAT_COL_DEV,
50 FSTAT_COL_INO,
51 FSTAT_COL_TYPE,
52 FSTAT_COL_MODE,
53 FSTAT_COL_NLINK,
54 FSTAT_COL_UID,
55 FSTAT_COL_USER,
56 FSTAT_COL_GID,
57 FSTAT_COL_GROUP,
58 FSTAT_COL_RDEV,
59 FSTAT_COL_SIZE,
60 FSTAT_COL_BLKSIZE,
61 FSTAT_COL_BLOCKS,
62 FSTAT_COL_ATIME,
63 FSTAT_COL_MTIME,
64 FSTAT_COL_CTIME,
65 FSTAT_COL_PATTERN,
66 };
67
68 struct fstat_table {
69 static constexpr const char *NAME = "fstat";
70 static constexpr const char *CREATE_STMT = R"(
71 CREATE TABLE fstat (
72 st_parent TEXT,
73 st_name TEXT,
74 st_dev INTEGER,
75 st_ino INTEGER,
76 st_type TEXT,
77 st_mode INTEGER,
78 st_nlink INTEGER,
79 st_uid TEXT,
80 st_user TEXT,
81 st_gid TEXT,
82 st_group TEXT,
83 st_rdev INTEGER,
84 st_size INTEGER,
85 st_blksize INTEGER,
86 st_blocks INTEGER,
87 st_atime DATETIME,
88 st_mtime DATETIME,
89 st_ctime DATETIME,
90 pattern TEXT HIDDEN
91 );
92 )";
93
94 struct cursor {
95 sqlite3_vtab_cursor base;
96 string c_pattern;
97 static_root_mem<glob_t, globfree> c_glob;
98 size_t c_path_index{0};
99 struct stat c_stat;
100
cursorfstat_table::cursor101 cursor(sqlite3_vtab *vt) : base({vt}) {
102 memset(&this->c_stat, 0, sizeof(this->c_stat));
103 };
104
load_statfstat_table::cursor105 void load_stat() {
106 while ((this->c_path_index < this->c_glob->gl_pathc) &&
107 lstat(this->c_glob->gl_pathv[this->c_path_index],
108 &this->c_stat) == -1) {
109 this->c_path_index += 1;
110 }
111 };
112
nextfstat_table::cursor113 int next() {
114 if (this->c_path_index < this->c_glob->gl_pathc) {
115 this->c_path_index += 1;
116 this->load_stat();
117 }
118
119 return SQLITE_OK;
120 };
121
resetfstat_table::cursor122 int reset() {
123 return SQLITE_OK;
124 }
125
eoffstat_table::cursor126 int eof() {
127 return this->c_path_index >= this->c_glob->gl_pathc;
128 };
129
get_rowidfstat_table::cursor130 int get_rowid(sqlite3_int64 &rowid_out) {
131 rowid_out = this->c_path_index;
132
133 return SQLITE_OK;
134 };
135 };
136
get_columnfstat_table137 int get_column(const cursor &vc, sqlite3_context *ctx, int col) {
138 const char *path = vc.c_glob->gl_pathv[vc.c_path_index];
139 char time_buf[32];
140
141 switch (col) {
142 case FSTAT_COL_PARENT: {
143 const char *slash = strrchr(path, '/');
144
145 if (slash == nullptr) {
146 sqlite3_result_text(ctx, ".", 1, SQLITE_STATIC);
147 } else if (path[1] == '\0') {
148 sqlite3_result_text(ctx, "", 0, SQLITE_STATIC);
149 } else {
150 sqlite3_result_text(ctx, path, slash - path + 1, SQLITE_TRANSIENT);
151 }
152 break;
153 }
154 case FSTAT_COL_NAME: {
155 const char *slash = strrchr(path, '/');
156
157 if (slash == nullptr) {
158 sqlite3_result_text(ctx, path, -1, SQLITE_TRANSIENT);
159 } else {
160 sqlite3_result_text(ctx, slash + 1, -1, SQLITE_TRANSIENT);
161 }
162 break;
163 }
164 case FSTAT_COL_DEV:
165 sqlite3_result_int(ctx, vc.c_stat.st_dev);
166 break;
167 case FSTAT_COL_INO:
168 sqlite3_result_int64(ctx, vc.c_stat.st_ino);
169 break;
170 case FSTAT_COL_TYPE:
171 if (S_ISREG(vc.c_stat.st_mode)) {
172 sqlite3_result_text(ctx, "reg", 3, SQLITE_STATIC);
173 } else if (S_ISBLK(vc.c_stat.st_mode)) {
174 sqlite3_result_text(ctx, "blk", 3, SQLITE_STATIC);
175 } else if (S_ISCHR(vc.c_stat.st_mode)) {
176 sqlite3_result_text(ctx, "chr", 3, SQLITE_STATIC);
177 } else if (S_ISDIR(vc.c_stat.st_mode)) {
178 sqlite3_result_text(ctx, "dir", 3, SQLITE_STATIC);
179 } else if (S_ISFIFO(vc.c_stat.st_mode)) {
180 sqlite3_result_text(ctx, "fifo", 4, SQLITE_STATIC);
181 } else if (S_ISLNK(vc.c_stat.st_mode)) {
182 sqlite3_result_text(ctx, "lnk", 3, SQLITE_STATIC);
183 } else if (S_ISSOCK(vc.c_stat.st_mode)) {
184 sqlite3_result_text(ctx, "sock", 3, SQLITE_STATIC);
185 }
186 break;
187 case FSTAT_COL_MODE:
188 sqlite3_result_int(ctx, vc.c_stat.st_mode & 0777);
189 break;
190 case FSTAT_COL_NLINK:
191 sqlite3_result_int(ctx, vc.c_stat.st_nlink);
192 break;
193 case FSTAT_COL_UID:
194 sqlite3_result_int(ctx, vc.c_stat.st_uid);
195 break;
196 case FSTAT_COL_USER: {
197 struct passwd *pw = getpwuid(vc.c_stat.st_uid);
198
199 if (pw != nullptr) {
200 sqlite3_result_text(ctx, pw->pw_name, -1, SQLITE_TRANSIENT);
201 } else {
202 sqlite3_result_int(ctx, vc.c_stat.st_uid);
203 }
204 break;
205 }
206 case FSTAT_COL_GID:
207 sqlite3_result_int(ctx, vc.c_stat.st_gid);
208 break;
209 case FSTAT_COL_GROUP: {
210 struct group *gr = getgrgid(vc.c_stat.st_gid);
211
212 if (gr != nullptr) {
213 sqlite3_result_text(ctx, gr->gr_name, -1, SQLITE_TRANSIENT);
214 } else {
215 sqlite3_result_int(ctx, vc.c_stat.st_gid);
216 }
217 break;
218 }
219 case FSTAT_COL_RDEV:
220 sqlite3_result_int(ctx, vc.c_stat.st_rdev);
221 break;
222 case FSTAT_COL_SIZE:
223 sqlite3_result_int64(ctx, vc.c_stat.st_size);
224 break;
225 case FSTAT_COL_BLKSIZE:
226 sqlite3_result_int(ctx, vc.c_stat.st_blksize);
227 break;
228 case FSTAT_COL_BLOCKS:
229 sqlite3_result_int(ctx, vc.c_stat.st_blocks);
230 break;
231 case FSTAT_COL_ATIME:
232 sql_strftime(time_buf, sizeof(time_buf), vc.c_stat.st_atime, 0);
233 sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
234 break;
235 case FSTAT_COL_MTIME:
236 sql_strftime(time_buf, sizeof(time_buf), vc.c_stat.st_mtime, 0);
237 sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
238 break;
239 case FSTAT_COL_CTIME:
240 sql_strftime(time_buf, sizeof(time_buf), vc.c_stat.st_ctime, 0);
241 sqlite3_result_text(ctx, time_buf, -1, SQLITE_TRANSIENT);
242 break;
243 case FSTAT_COL_PATTERN:
244 sqlite3_result_text(ctx,
245 vc.c_pattern.c_str(),
246 vc.c_pattern.length(),
247 SQLITE_TRANSIENT);
248 break;
249 }
250
251 return SQLITE_OK;
252 }
253
254 #if 0
255 int update_row(sqlite3_vtab *tab,
256 sqlite3_int64 &index,
257 const char *st_parent,
258 const char *st_name,
259 int64_t st_dev,
260 int64_t st_ino,
261 const char *st_type,
262 int64_t st_mode,
263 int64_t st_nlink,
264 int64_t st_uid,
265 const char *st_user,
266 int64_t st_gid,
267 const char *st_group,
268 int64_t st_rdev,
269 int64_t st_size,
270 int64_t st_blksize,
271 int64_t st_blocks,
272 const char *atime,
273 const char *mtime,
274 const char *ctime,
275 const char *pattern) {
276
277 };
278 #endif
279 };
280
rcBestIndex(sqlite3_vtab * tab,sqlite3_index_info * pIdxInfo)281 static int rcBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo)
282 {
283 vtab_index_constraints vic(pIdxInfo);
284 vtab_index_usage viu(pIdxInfo);
285
286 for (auto iter = vic.begin(); iter != vic.end(); ++iter) {
287 if (iter->op != SQLITE_INDEX_CONSTRAINT_EQ) {
288 continue;
289 }
290
291 switch (iter->iColumn) {
292 case FSTAT_COL_PATTERN:
293 viu.column_used(iter);
294 break;
295 }
296 }
297
298 viu.allocate_args(1);
299 return SQLITE_OK;
300 }
301
rcFilter(sqlite3_vtab_cursor * pVtabCursor,int idxNum,const char * idxStr,int argc,sqlite3_value ** argv)302 static int rcFilter(sqlite3_vtab_cursor *pVtabCursor,
303 int idxNum, const char *idxStr,
304 int argc, sqlite3_value **argv)
305 {
306 fstat_table::cursor *pCur = (fstat_table::cursor *)pVtabCursor;
307
308 if (argc != 1) {
309 pCur->c_pattern.clear();
310 return SQLITE_OK;
311 }
312
313 const char *pattern = (const char *) sqlite3_value_text(argv[0]);
314 pCur->c_pattern = pattern;
315 switch (glob(pattern,
316 #ifdef GLOB_TILDE
317 GLOB_TILDE|
318 #endif
319 GLOB_ERR,
320 nullptr,
321 pCur->c_glob.inout())) {
322 case GLOB_NOSPACE:
323 pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
324 "No space to perform glob()");
325 return SQLITE_ERROR;
326 case GLOB_NOMATCH:
327 return SQLITE_OK;
328 }
329
330 pCur->load_stat();
331
332 return SQLITE_OK;
333 }
334
register_fstat_vtab(sqlite3 * db)335 int register_fstat_vtab(sqlite3 *db)
336 {
337 static vtab_module<tvt_no_update<fstat_table>> FSTAT_MODULE;
338
339 int rc;
340
341 FSTAT_MODULE.vm_module.xBestIndex = rcBestIndex;
342 FSTAT_MODULE.vm_module.xFilter = rcFilter;
343
344 rc = FSTAT_MODULE.create(db, "fstat");
345
346 ensure(rc == SQLITE_OK);
347
348 return rc;
349 }
350