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