1 /**
2  * Copyright (c) 2013, 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  * @file fs-extension-functions.cc
30  */
31 
32 #include "config.h"
33 
34 #include <errno.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 
39 #include <string>
40 #include <stddef.h>
41 
42 #include "sqlite3.h"
43 
44 #include "sqlite-extension-func.hh"
45 
46 #include "vtab_module.hh"
47 
48 using namespace std;
49 using namespace mapbox;
50 
51 static
52 util::variant<const char *, string_fragment>
sql_basename(const char * path_in)53 sql_basename(const char *path_in)
54 {
55     int text_end = -1;
56 
57     if (path_in[0] == '\0') {
58         return ".";
59     }
60 
61     for (ssize_t lpc = strlen(path_in) - 1; lpc >= 0; lpc--) {
62         if (path_in[lpc] == '/' || path_in[lpc] == '\\') {
63             if (text_end != -1) {
64                 return string_fragment(path_in, lpc + 1, text_end);
65             }
66         }
67         else if (text_end == -1) {
68             text_end = (int) (lpc + 1);
69         }
70     }
71 
72     if (text_end == -1) {
73         return "/";
74     }
75     else {
76         return string_fragment(path_in, 0, text_end);
77     }
78 }
79 
80 static
81 util::variant<const char *, string_fragment>
sql_dirname(const char * path_in)82 sql_dirname(const char *path_in)
83 {
84     ssize_t     text_end;
85 
86     text_end = strlen(path_in) - 1;
87     while (text_end >= 0 &&
88            (path_in[text_end] == '/' || path_in[text_end] == '\\')) {
89         text_end -= 1;
90     }
91 
92     while (text_end >= 0) {
93         if (path_in[text_end] == '/' || path_in[text_end] == '\\') {
94             return string_fragment(path_in, 0, text_end == 0 ? 1 : text_end);
95         }
96 
97         text_end -= 1;
98     }
99 
100     return path_in[0] == '/' ? "/" : ".";
101 }
102 
103 static
sql_joinpath(const vector<const char * > & paths)104 nonstd::optional<string> sql_joinpath(const vector<const char *> &paths)
105 {
106     std::string full_path;
107 
108     if (paths.empty()) {
109         return nonstd::nullopt;
110     }
111 
112     for (auto &path_in : paths) {
113         if (path_in == nullptr) {
114             return nonstd::nullopt;
115         }
116 
117         if (path_in[0] == '/' || path_in[0] == '\\') {
118             full_path.clear();
119         }
120         if (!full_path.empty() &&
121             full_path[full_path.length() - 1] != '/' &&
122             full_path[full_path.length() - 1] != '\\') {
123             full_path += "/";
124         }
125         full_path += path_in;
126     }
127 
128     return full_path;
129 }
130 
131 static
sql_readlink(const char * path)132 string sql_readlink(const char *path)
133 {
134     struct stat st;
135 
136     if (lstat(path, &st) == -1) {
137         throw sqlite_func_error("unable to stat path: {} -- {}", path, strerror(errno));
138     }
139 
140     char buf[st.st_size];
141     ssize_t rc;
142 
143     rc = readlink(path, buf, sizeof(buf));
144     if (rc < 0) {
145         if (errno == EINVAL) {
146             return path;
147         }
148         throw sqlite_func_error("unable to read link: {} -- {}", path, strerror(errno));
149     }
150 
151     return string(buf, rc);
152 }
153 
154 static
sql_realpath(const char * path)155 string sql_realpath(const char *path)
156 {
157     char resolved_path[PATH_MAX];
158 
159     if (realpath(path, resolved_path) == nullptr) {
160         throw sqlite_func_error("Could not get real path for {} -- {}",
161                                 path, strerror(errno));
162     }
163 
164     return resolved_path;
165 }
166 
167 
fs_extension_functions(struct FuncDef ** basic_funcs,struct FuncDefAgg ** agg_funcs)168 int fs_extension_functions(struct FuncDef **basic_funcs,
169                            struct FuncDefAgg **agg_funcs)
170 {
171     static struct FuncDef fs_funcs[] = {
172 
173         sqlite_func_adapter<decltype(&sql_basename), sql_basename>::builder(
174             help_text("basename",
175                       "Extract the base portion of a pathname.")
176                 .sql_function()
177                 .with_parameter({"path", "The path"})
178                 .with_tags({"filename"})
179                 .with_example({
180                     "To get the base of a plain file name",
181                     "SELECT basename('foobar')"
182                 })
183                 .with_example({
184                     "To get the base of a path",
185                     "SELECT basename('foo/bar')"
186                 })
187                 .with_example({
188                     "To get the base of a directory",
189                     "SELECT basename('foo/bar/')"
190                 })
191                 .with_example({
192                     "To get the base of an empty string",
193                     "SELECT basename('')"
194                 })
195                 .with_example({
196                     "To get the base of a Windows path",
197                     "SELECT basename('foo\\bar')"
198                 })
199                 .with_example({
200                     "To get the base of the root directory",
201                     "SELECT basename('/')"
202                 })
203         ),
204 
205         sqlite_func_adapter<decltype(&sql_dirname), sql_dirname>::builder(
206             help_text("dirname",
207                       "Extract the directory portion of a pathname.")
208                 .sql_function()
209                 .with_parameter({"path", "The path"})
210                 .with_tags({"filename"})
211                 .with_example({
212                     "To get the directory of a relative file path",
213                     "SELECT dirname('foo/bar')"
214                 })
215                 .with_example({
216                     "To get the directory of an absolute file path",
217                     "SELECT dirname('/foo/bar')"
218                 })
219                 .with_example({
220                     "To get the directory of a file in the root directory",
221                     "SELECT dirname('/bar')"
222                 })
223                 .with_example({
224                     "To get the directory of a Windows path",
225                     "SELECT dirname('foo\\bar')"
226                 })
227                 .with_example({
228                     "To get the directory of an empty path",
229                     "SELECT dirname('')"
230                 })
231         ),
232 
233         sqlite_func_adapter<decltype(&sql_joinpath), sql_joinpath>::builder(
234             help_text("joinpath",
235                       "Join components of a path together.")
236                 .sql_function()
237                 .with_parameter(help_text("path", "One or more path components to join together.  "
238                     "If an argument starts with a forward or backward slash, it will be considered "
239                     "an absolute path and any preceding elements will be ignored.")
240                                     .one_or_more())
241                 .with_tags({"filename"})
242                 .with_example({
243                     "To join a directory and file name into a relative path",
244                     "SELECT joinpath('foo', 'bar')"
245                 })
246                 .with_example({
247                     "To join an empty component with other names into a relative path",
248                     "SELECT joinpath('', 'foo', 'bar')"
249                 })
250                 .with_example({
251                     "To create an absolute path with two path components",
252                     "SELECT joinpath('/', 'foo', 'bar')"
253                 })
254                 .with_example({
255                     "To create an absolute path from a path component that starts with a forward slash",
256                     "SELECT joinpath('/', 'foo', '/bar')"
257                 })
258         ),
259 
260         sqlite_func_adapter<decltype(&sql_readlink), sql_readlink>::builder(
261             help_text("readlink",
262                       "Read the target of a symbolic link.")
263                 .sql_function()
264                 .with_parameter({"path", "The path to the symbolic link."})
265                 .with_tags({"filename"})
266         ),
267 
268         sqlite_func_adapter<decltype(&sql_realpath), sql_realpath>::builder(
269             help_text("realpath",
270                       "Returns the resolved version of the given path, expanding symbolic links and "
271                           "resolving '.' and '..' references.")
272                 .sql_function()
273                 .with_parameter({"path", "The path to resolve."})
274                 .with_tags({"filename"})
275         ),
276 
277         /*
278          * TODO: add other functions like normpath, ...
279          */
280 
281         { nullptr }
282     };
283 
284     *basic_funcs = fs_funcs;
285     *agg_funcs   = nullptr;
286 
287     return SQLITE_OK;
288 }
289