Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #ifndef HAVE_KDBCONFIG
10 : #include "kdbconfig.h"
11 : #endif
12 :
13 : #include <kdbproposal.h>
14 :
15 : #include <kdbassert.h>
16 : #include <kdberrors.h>
17 :
18 : #include <string.h>
19 :
20 : #include "wresolver.h"
21 : #include <errno.h> /* errno in getcwd() */
22 : #include <stdlib.h>
23 : #include <sys/stat.h> /* mkdir() */
24 : #include <unistd.h> /* getcwd() */
25 :
26 : #if defined(_WIN32)
27 : #include <io.h>
28 : #include <shlobj.h>
29 : #include <windows.h>
30 : #endif
31 :
32 : /**
33 : * @retval 1 on success (Relative path)
34 : * @retval 0 on success (Absolute path)
35 : * @retval never -1 (success guaranteed)
36 : */
37 20 : int elektraWresolverCheckFile (const char * filename)
38 : {
39 20 : if (filename[0] == '/') return 0;
40 :
41 10 : return 1;
42 : }
43 :
44 : typedef struct _resolverHandle resolverHandle;
45 :
46 : struct _resolverHandle
47 : {
48 : time_t mtime; ///< Previous timestamp of the file
49 : mode_t mode; ///< The mode to set
50 : int state; ///< 0 .. invalid -> kdbGet -> 1 .. ready to set -> kdbSet -> 2
51 :
52 : char * filename; ///< the full path to the configuration file
53 :
54 : const char * path;
55 : };
56 :
57 : typedef struct _resolverHandles resolverHandles;
58 :
59 : struct _resolverHandles
60 : {
61 : resolverHandle spec;
62 : resolverHandle dir;
63 : resolverHandle user;
64 : resolverHandle system;
65 : };
66 :
67 10 : static resolverHandle * elektraGetResolverHandle (Plugin * handle, Key * parentKey)
68 : {
69 10 : resolverHandles * pks = elektraPluginGetData (handle);
70 10 : switch (keyGetNamespace (parentKey))
71 : {
72 : case KEY_NS_SPEC:
73 2 : return &pks->spec;
74 : case KEY_NS_DIR:
75 4 : return &pks->dir;
76 : case KEY_NS_USER:
77 2 : return &pks->user;
78 : case KEY_NS_SYSTEM:
79 2 : return &pks->system;
80 : case KEY_NS_PROC:
81 : case KEY_NS_EMPTY:
82 : case KEY_NS_NONE:
83 : case KEY_NS_META:
84 : case KEY_NS_CASCADING:
85 : break;
86 : }
87 0 : ELEKTRA_ASSERT (0, "namespace %d not valid for resolving", keyGetNamespace (parentKey));
88 : return 0;
89 : }
90 :
91 :
92 : static void resolverClose (resolverHandle * p)
93 : {
94 960 : elektraFree (p->filename);
95 960 : p->filename = 0;
96 : }
97 :
98 : static void resolverInit (resolverHandle * p, const char * path)
99 : {
100 960 : p->mtime = 0;
101 960 : p->mode = 0;
102 960 : p->state = 0;
103 :
104 960 : p->filename = 0;
105 :
106 960 : p->path = path;
107 : }
108 :
109 176 : static void escapePath (char * home)
110 : {
111 176 : int len = strlen (home), i;
112 528 : for (i = 0; i < len; ++i)
113 : {
114 352 : if (home[i] == '\\')
115 : {
116 0 : home[i] = '/';
117 : }
118 : }
119 176 : }
120 :
121 240 : static void elektraResolveSpec (resolverHandle * p, Key * errorKey)
122 : {
123 240 : char * system = getenv ("ALLUSERSPROFILE");
124 :
125 240 : if (!system)
126 : {
127 152 : system = "";
128 152 : ELEKTRA_ADD_INSTALLATION_WARNING (
129 : errorKey, "Could not retrieve from passwd using getpwuid_r. Could not get ALLUSERSPROFILE for spec, using /");
130 : }
131 : else
132 : {
133 88 : escapePath (system);
134 : }
135 :
136 :
137 240 : if (p->path[0] == '/')
138 : {
139 : /* Use absolute path */
140 20 : size_t filenameSize = strlen (system) + strlen (p->path) + 1;
141 20 : p->filename = elektraMalloc (filenameSize);
142 20 : strcpy (p->filename, system);
143 20 : strcat (p->filename, p->path);
144 : return;
145 : }
146 220 : size_t filenameSize = sizeof (KDB_DB_SPEC) + strlen (system) + strlen (p->path) + sizeof ("/") + 1;
147 220 : p->filename = elektraMalloc (filenameSize);
148 220 : strcpy (p->filename, system);
149 220 : strcat (p->filename, KDB_DB_SPEC);
150 220 : strcat (p->filename, "/");
151 220 : strcat (p->filename, p->path);
152 : return;
153 : }
154 :
155 240 : static void elektraResolveDir (resolverHandle * p, Key * warningsKey)
156 : {
157 240 : p->filename = elektraMalloc (KDB_MAX_PATH_LENGTH);
158 :
159 : #if defined(_WIN32)
160 : CHAR dir[MAX_PATH];
161 : DWORD dwRet = GetCurrentDirectory (MAX_PATH, dir);
162 : if (dwRet == 0)
163 : {
164 : char buf[256];
165 : FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError (), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256,
166 : NULL);
167 : ELEKTRA_ADD_RESOURCE_WARNINGF (warningsKey, "GetCurrentDirectory failed: %s, defaulting to /", buf);
168 : dir[0] = 0;
169 : }
170 : else if (dwRet > MAX_PATH)
171 : {
172 : // TODO: Solution? Compile with higher MAX_PATH or submit a bug to the bug tracker and let us do it for you
173 : ELEKTRA_ADD_INSTALLATION_WARNINGF (warningsKey, "GetCurrentDirectory failed, buffer size too small, needed: %ld", dwRet);
174 : dir[0] = 0;
175 : }
176 : escapePath (dir);
177 : #else
178 : char dir[KDB_MAX_PATH_LENGTH];
179 240 : if (getcwd (dir, KDB_MAX_PATH_LENGTH) == 0)
180 : {
181 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (warningsKey, "Command 'getcwd' failed. Defaulting to /. Reason: %s", strerror (errno));
182 0 : dir[0] = 0;
183 : }
184 : #endif
185 :
186 240 : strcpy (p->filename, dir);
187 240 : strcat (p->filename, "/");
188 240 : strncat (p->filename, p->path, KDB_MAX_PATH_LENGTH - strlen (dir) - 3);
189 240 : p->filename[KDB_MAX_PATH_LENGTH - 1] = 0;
190 :
191 240 : return;
192 : }
193 :
194 240 : static void elektraResolveUser (resolverHandle * p, Key * warningsKey)
195 : {
196 240 : p->filename = elektraMalloc (KDB_MAX_PATH_LENGTH);
197 :
198 : #if defined(_WIN32)
199 : CHAR home[MAX_PATH];
200 : if (SUCCEEDED (SHGetFolderPath (NULL, CSIDL_PROFILE, NULL, 0, home)))
201 : {
202 : escapePath (home);
203 : }
204 : else
205 : {
206 : strcpy (home, "");
207 : ELEKTRA_ADD_INSTALLATION_WARNING (warningsKey, "Could not get home (CSIDL_PROFILE), using /");
208 : }
209 : #else
210 240 : char * home = (char *) getenv ("HOME");
211 240 : if (!home)
212 : {
213 176 : home = "";
214 176 : ELEKTRA_ADD_INSTALLATION_WARNING (warningsKey, "Could not get HOME environment variable, using /");
215 : }
216 : #endif
217 :
218 240 : strcpy (p->filename, home);
219 240 : strcat (p->filename, "/");
220 240 : strncat (p->filename, p->path, KDB_MAX_PATH_LENGTH);
221 240 : }
222 :
223 240 : static void elektraResolveSystem (resolverHandle * p, Key * errorKey)
224 : {
225 240 : char * system = getenv ("ALLUSERSPROFILE");
226 :
227 240 : if (!system)
228 : {
229 152 : system = "";
230 152 : ELEKTRA_ADD_INSTALLATION_WARNING (errorKey, "Could not get ALLUSERSPROFILE environment variable, using /");
231 : }
232 : else
233 : {
234 88 : escapePath (system);
235 : }
236 :
237 240 : if (p->path[0] == '/')
238 : {
239 : /* Use absolute path */
240 20 : size_t filenameSize = strlen (system) + strlen (p->path) + 1;
241 20 : p->filename = elektraMalloc (filenameSize);
242 20 : strcpy (p->filename, system);
243 20 : strcat (p->filename, p->path);
244 : return;
245 : }
246 220 : size_t filenameSize = sizeof (KDB_DB_SYSTEM) + strlen (system) + strlen (p->path) + sizeof ("/") + 1;
247 220 : p->filename = elektraMalloc (filenameSize);
248 220 : strcpy (p->filename, system);
249 220 : strcat (p->filename, KDB_DB_SYSTEM);
250 220 : strcat (p->filename, "/");
251 220 : strcat (p->filename, p->path);
252 : return;
253 : }
254 :
255 240 : int elektraWresolverOpen (Plugin * handle, Key * errorKey)
256 : {
257 240 : KeySet * resolverConfig = elektraPluginGetConfig (handle);
258 240 : const char * path = keyString (ksLookupByName (resolverConfig, "/path", 0));
259 :
260 240 : if (!path)
261 : {
262 0 : ELEKTRA_SET_RESOURCE_ERROR (errorKey, "Could not find file configuration");
263 0 : return -1;
264 : }
265 :
266 240 : resolverHandles * p = elektraMalloc (sizeof (resolverHandles));
267 :
268 : // switch is only present to forget no namespace and to get
269 : // a warning whenever a new namespace is present.
270 : // (also used below in close)
271 : // In fact its linear code executed:
272 : switch (KEY_NS_SPEC)
273 : {
274 : case KEY_NS_SPEC:
275 480 : resolverInit (&p->spec, path);
276 240 : elektraResolveSpec (&p->spec, errorKey);
277 : // FALLTHROUGH
278 : case KEY_NS_DIR:
279 480 : resolverInit (&p->dir, path);
280 240 : elektraResolveDir (&p->dir, errorKey);
281 : // FALLTHROUGH
282 : case KEY_NS_USER:
283 480 : resolverInit (&p->user, path);
284 240 : elektraResolveUser (&p->user, errorKey);
285 : // FALLTHROUGH
286 : case KEY_NS_SYSTEM:
287 480 : resolverInit (&p->system, path);
288 240 : elektraResolveSystem (&p->system, errorKey);
289 : // FALLTHROUGH
290 : case KEY_NS_PROC:
291 : case KEY_NS_EMPTY:
292 : case KEY_NS_NONE:
293 : case KEY_NS_META:
294 : case KEY_NS_CASCADING:
295 : break;
296 : }
297 :
298 240 : elektraPluginSetData (handle, p);
299 :
300 240 : return 0; /* success */
301 : }
302 :
303 240 : int elektraWresolverClose (Plugin * handle, Key * errorKey ELEKTRA_UNUSED)
304 : {
305 240 : resolverHandles * ps = elektraPluginGetData (handle);
306 :
307 240 : if (ps)
308 : {
309 : switch (KEY_NS_SPEC)
310 : {
311 : case KEY_NS_SPEC:
312 240 : resolverClose (&ps->spec); // FALLTHROUGH
313 : case KEY_NS_DIR:
314 240 : resolverClose (&ps->dir); // FALLTHROUGH
315 : case KEY_NS_USER:
316 240 : resolverClose (&ps->user); // FALLTHROUGH
317 : case KEY_NS_SYSTEM:
318 240 : resolverClose (&ps->system); // FALLTHROUGH
319 : case KEY_NS_PROC:
320 : case KEY_NS_EMPTY:
321 : case KEY_NS_NONE:
322 : case KEY_NS_META:
323 : case KEY_NS_CASCADING:
324 : break;
325 : }
326 :
327 240 : elektraFree (ps);
328 240 : elektraPluginSetData (handle, 0);
329 : }
330 :
331 240 : return 0; /* success */
332 : }
333 :
334 170 : int elektraWresolverGet (Plugin * handle, KeySet * returned, Key * parentKey)
335 : {
336 170 : if (!strcmp (keyName (parentKey), "system/elektra/modules/wresolver"))
337 : {
338 160 : KeySet * contract = ksNew (
339 : 30, keyNew ("system/elektra/modules/wresolver", KEY_VALUE, "wresolver plugin waits for your orders", KEY_END),
340 : keyNew ("system/elektra/modules/wresolver/exports", KEY_END),
341 : keyNew ("system/elektra/modules/wresolver/exports/open", KEY_FUNC, elektraWresolverOpen, KEY_END),
342 : keyNew ("system/elektra/modules/wresolver/exports/close", KEY_FUNC, elektraWresolverClose, KEY_END),
343 : keyNew ("system/elektra/modules/wresolver/exports/get", KEY_FUNC, elektraWresolverGet, KEY_END),
344 : keyNew ("system/elektra/modules/wresolver/exports/set", KEY_FUNC, elektraWresolverSet, KEY_END),
345 : keyNew ("system/elektra/modules/wresolver/exports/error", KEY_FUNC, elektraWresolverError, KEY_END),
346 : keyNew ("system/elektra/modules/wresolver/exports/checkfile", KEY_FUNC, elektraWresolverCheckFile, KEY_END),
347 : #include ELEKTRA_README
348 : keyNew ("system/elektra/modules/wresolver/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
349 160 : ksAppend (returned, contract);
350 160 : ksDel (contract);
351 :
352 160 : return 1; /* success */
353 : }
354 : /* get all keys */
355 :
356 10 : resolverHandle * pk = elektraGetResolverHandle (handle, parentKey);
357 10 : keySetString (parentKey, pk->filename);
358 :
359 10 : pk->state = 1;
360 :
361 : struct stat buf;
362 :
363 20 : if (stat (pk->filename, &buf) == -1)
364 : {
365 : // no file, so storage has no job
366 10 : pk->mtime = 0; // no file, so no time
367 10 : return 0;
368 : }
369 :
370 : /* Check if update needed */
371 0 : if (pk->mtime == buf.st_mtime)
372 : {
373 : // no update, so storage has no job
374 : return 0;
375 : }
376 :
377 0 : pk->mtime = buf.st_mtime;
378 :
379 0 : return 1; /* success */
380 : }
381 :
382 0 : int elektraWresolverSet (Plugin * handle, KeySet * returned ELEKTRA_UNUSED, Key * parentKey)
383 : {
384 0 : resolverHandle * pk = elektraGetResolverHandle (handle, parentKey);
385 0 : keySetString (parentKey, pk->filename);
386 :
387 0 : switch (pk->state)
388 : {
389 : case 0:
390 0 : ELEKTRA_SET_CONFLICTING_STATE_ERROR (parentKey, "Command 'kdbSet()' called before 'kdbGet()'");
391 0 : return -1;
392 : case 1:
393 0 : ++pk->state;
394 0 : break;
395 : case 2:
396 0 : pk->state = 1;
397 : // nothing to do on commit
398 0 : return 1;
399 : }
400 :
401 : /* set all keys */
402 0 : if (pk->mtime == 0)
403 : {
404 : // this can happen if the kdbGet() path found no file
405 :
406 : // no conflict possible, so just return successfully
407 : return 1;
408 : }
409 :
410 : struct stat buf;
411 :
412 0 : if (stat (pk->filename, &buf) == -1)
413 : {
414 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Could not stat config file '%s'", pk->filename);
415 : // no file found, nothing to do
416 0 : return 0;
417 : }
418 :
419 : /* Check for conflict */
420 0 : if (pk->mtime != buf.st_mtime)
421 : {
422 : // conflict
423 0 : ELEKTRA_SET_CONFLICTING_STATE_ERRORF (
424 : parentKey,
425 : "Conflict, file modification time stamp %ld is different than our time stamp %ld config file name is '%s'",
426 : (long) buf.st_mtime, (long) pk->mtime, pk->filename);
427 0 : pk->state = 0; // invalid state, need to kdbGet again
428 0 : return -1;
429 : }
430 :
431 0 : pk->mtime = buf.st_mtime;
432 :
433 0 : return 1; /* success */
434 : }
435 :
436 0 : int elektraWresolverError (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
437 : {
438 0 : resolverHandle * pk = elektraGetResolverHandle (handle, parentKey);
439 0 : pk->state = 1;
440 :
441 :
442 0 : return 1; /* success */
443 : }
444 :
445 240 : Plugin * ELEKTRA_PLUGIN_EXPORT
446 : {
447 : // clang-format off
448 240 : return elektraPluginExport("wresolver",
449 : ELEKTRA_PLUGIN_OPEN, &elektraWresolverOpen,
450 : ELEKTRA_PLUGIN_CLOSE, &elektraWresolverClose,
451 : ELEKTRA_PLUGIN_GET, &elektraWresolverGet,
452 : ELEKTRA_PLUGIN_SET, &elektraWresolverSet,
453 : ELEKTRA_PLUGIN_ERROR, &elektraWresolverError,
454 : ELEKTRA_PLUGIN_END);
455 : }
456 :
|