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