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 : #include "resolver.h"
10 :
11 : #include <kdbassert.h>
12 : #include <kdbconfig.h>
13 : #include <kdbhelper.h> // elektraStrDup
14 : #include <kdbprivate.h> // KDB_CACHE_PREFIX
15 : #include <kdbproposal.h>
16 :
17 : #include "kdbos.h"
18 :
19 : #include <stdlib.h>
20 :
21 : #ifdef HAVE_CTYPE_H
22 : #include <ctype.h>
23 : #endif
24 :
25 : /* Needs posix */
26 : #include <errno.h>
27 : #include <fcntl.h>
28 : #include <stdio.h>
29 : #include <string.h>
30 : #include <sys/stat.h>
31 : #include <sys/time.h>
32 : #include <unistd.h>
33 :
34 : #include <dirent.h>
35 :
36 : #include <kdberrors.h>
37 : #include <kdblogger.h>
38 : #include <kdbmacros.h>
39 :
40 : #ifdef ELEKTRA_LOCK_MUTEX
41 : #include <pthread.h>
42 : #endif
43 :
44 : #ifdef ELEKTRA_LOCK_MUTEX
45 : #if defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP)
46 : static pthread_mutex_t elektraResolverMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
47 : #elif defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER)
48 : static pthread_mutex_t elektraResolverMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
49 : #else
50 : static pthread_mutex_t elektraResolverMutex;
51 : static pthread_mutex_t elektraResolverInitMutex = PTHREAD_MUTEX_INITIALIZER;
52 : static unsigned char elektraResolverMutexInitialized = 0;
53 : #define ELEKTRA_RESOLVER_RECURSIVE_MUTEX_INITIALIZATION
54 : #endif
55 : #endif
56 :
57 : static void resolverInit (resolverHandle * p, const char * path)
58 : {
59 171060 : p->fd = -1;
60 171060 : p->mtime.tv_sec = 0;
61 171060 : p->mtime.tv_nsec = 0;
62 85530 : p->filemode = KDB_FILE_MODE;
63 85530 : p->dirmode = KDB_FILE_MODE | KDB_DIR_MODE;
64 171060 : p->removalNeeded = 0;
65 171060 : p->isMissing = 0;
66 171060 : p->timeFix = 1;
67 :
68 171060 : p->filename = 0;
69 171060 : p->dirname = 0;
70 171060 : p->tempfile = 0;
71 :
72 171060 : p->path = path;
73 :
74 171060 : p->uid = 0;
75 171060 : p->gid = 0;
76 : }
77 :
78 66355 : static resolverHandle * elektraGetResolverHandle (Plugin * handle, Key * parentKey)
79 : {
80 66355 : resolverHandles * pks = elektraPluginGetData (handle);
81 66355 : ELEKTRA_ASSERT (pks != NULL, "Unable to retrieve plugin data for handle %p with parentKey %s", (void *) handle,
82 : keyName (parentKey));
83 :
84 66355 : switch (keyGetNamespace (parentKey))
85 : {
86 : case KEY_NS_SPEC:
87 12008 : return &pks->spec;
88 : case KEY_NS_DIR:
89 11866 : return &pks->dir;
90 : case KEY_NS_USER:
91 15501 : return &pks->user;
92 : case KEY_NS_SYSTEM:
93 26980 : return &pks->system;
94 : case KEY_NS_PROC:
95 : case KEY_NS_EMPTY:
96 : case KEY_NS_NONE:
97 : case KEY_NS_META:
98 : case KEY_NS_CASCADING:
99 : return 0;
100 : }
101 :
102 : return 0;
103 : }
104 :
105 :
106 171060 : static void resolverCloseOne (resolverHandle * p)
107 : {
108 171060 : elektraFree (p->filename);
109 171060 : p->filename = 0;
110 171060 : elektraFree (p->dirname);
111 171060 : p->dirname = 0;
112 171060 : elektraFree (p->tempfile);
113 171060 : p->tempfile = 0;
114 171060 : }
115 :
116 42765 : static void resolverClose (resolverHandles * p)
117 : {
118 42765 : resolverCloseOne (&p->spec);
119 42765 : resolverCloseOne (&p->dir);
120 42765 : resolverCloseOne (&p->user);
121 42765 : resolverCloseOne (&p->system);
122 42765 : elektraFree (p);
123 42765 : }
124 :
125 : /**
126 : * Locks file for exclusive read/write mode.
127 : *
128 : * This function will not block until all reader
129 : * and writer have left the file.
130 : * -> conflict with other cooperative process detected,
131 : * but we were later (and lost)
132 : *
133 : * @exception 27 set if locking failed, most likely a conflict
134 : *
135 : * @param fd is a valid filedescriptor
136 : * @retval 0 on success
137 : * @retval -1 on failure
138 : * @ingroup backendhelper
139 : */
140 4816 : static int elektraLockFile (int fd ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
141 : {
142 : #ifdef ELEKTRA_LOCK_FILE
143 : struct flock l;
144 4816 : l.l_type = F_WRLCK; /*Do exclusive Lock*/
145 4816 : l.l_start = 0; /*Start at begin*/
146 4816 : l.l_whence = SEEK_SET;
147 4816 : l.l_len = 0; /*Do it with whole file*/
148 4816 : int ret = fcntl (fd, F_SETLK, &l);
149 :
150 4816 : if (ret == -1)
151 : {
152 0 : if (errno == EAGAIN || errno == EACCES)
153 : {
154 0 : ELEKTRA_SET_RESOURCE_ERROR (parentKey,
155 : "Conflict because other process writes to configuration indicated by file lock");
156 : }
157 : else
158 : {
159 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Assuming conflict because of failed file lock. Reason: %s",
160 : strerror (errno));
161 : }
162 : return -1;
163 : }
164 :
165 : return ret;
166 : #else
167 : return 0;
168 : #endif
169 : }
170 :
171 :
172 : /**
173 : * Unlocks file.
174 : *
175 : * @param fd is a valid filedescriptor
176 : * @retval 0 on success
177 : * @retval -1 on failure
178 : * @ingroup backendhelper
179 : */
180 4816 : static int elektraUnlockFile (int fd ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
181 : {
182 : #ifdef ELEKTRA_LOCK_FILE
183 : struct flock l;
184 4816 : l.l_type = F_UNLCK; /*Give Lock away*/
185 4816 : l.l_start = 0; /*Start at begin*/
186 4816 : l.l_whence = SEEK_SET;
187 4816 : l.l_len = 0; /*Do it with whole file*/
188 4816 : int ret = fcntl (fd, F_SETLK, &l);
189 :
190 4816 : if (ret == -1)
191 : {
192 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Method 'fcntl' unlocking failed (SETLK). Reason: %s", strerror (errno));
193 : }
194 :
195 4816 : return ret;
196 : #else
197 : return 0;
198 : #endif
199 : }
200 :
201 : /**
202 : * @brief mutex lock for multithread-safety
203 : *
204 : * @retval 0 on success
205 : * @retval -1 on error
206 : */
207 2445 : static int elektraLockMutex (Key * parentKey ELEKTRA_UNUSED)
208 : {
209 : #ifdef ELEKTRA_LOCK_MUTEX
210 2445 : int ret = pthread_mutex_trylock (&elektraResolverMutex);
211 2445 : if (ret != 0)
212 : {
213 0 : if (errno == EBUSY // for trylock
214 0 : || errno == EDEADLK) // for error checking mutex, if enabled
215 : {
216 0 : ELEKTRA_SET_CONFLICTING_STATE_ERROR (
217 : parentKey, "Conflict because other thread writes to configuration indicated by mutex lock");
218 : }
219 : else
220 : {
221 0 : ELEKTRA_SET_CONFLICTING_STATE_ERRORF (parentKey, "Assuming conflict because of failed mutex lock. Reason: %s",
222 : strerror (errno));
223 : }
224 : return -1;
225 : }
226 : return 0;
227 : #else
228 : return 0;
229 : #endif
230 : }
231 :
232 : /**
233 : * @brief mutex unlock for multithread-safety
234 : *
235 : * @retval 0 on success
236 : * @retval -1 on error
237 : */
238 2445 : static int elektraUnlockMutex (Key * parentKey ELEKTRA_UNUSED)
239 : {
240 : #ifdef ELEKTRA_LOCK_MUTEX
241 2445 : int ret = pthread_mutex_unlock (&elektraResolverMutex);
242 2445 : if (ret != 0)
243 : {
244 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Mutex unlock failed. Reason: %s", strerror (errno));
245 0 : return -1;
246 : }
247 : return 0;
248 : #else
249 : return 0;
250 : #endif
251 : }
252 :
253 :
254 : /**
255 : * @brief Close a file
256 : *
257 : * @param fd the filedescriptor to close
258 : * @param parentKey the key to write warnings to
259 : */
260 4816 : static void elektraCloseFile (int fd, Key * parentKey)
261 : {
262 4816 : if (close (fd) == -1)
263 : {
264 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Close file failed. Reason: %s", strerror (errno));
265 : }
266 4816 : }
267 :
268 : /**
269 : * @brief Add error text received from strerror
270 : *
271 : * @param errorText should have at least ERROR_SIZE bytes in reserve
272 : */
273 0 : static char * elektraAddErrnoText (void)
274 : {
275 0 : if (errno == E2BIG)
276 : {
277 : return "could not find a / in the pathname";
278 : }
279 0 : else if (errno == EINVAL)
280 : {
281 : return "went up to root for creating directory";
282 : }
283 : else
284 : {
285 0 : return strerror (errno);
286 : }
287 : #if defined(__GNUC__) && __GNUC__ >= 8 && !defined(__clang__)
288 : #pragma GCC diagnostic push
289 : #pragma GCC diagnostic ignored "-Wstringop-truncation"
290 : #endif
291 : #if defined(__GNUC__) && __GNUC__ >= 8 && !defined(__clang__)
292 : #pragma GCC diagnostic pop
293 : #endif
294 : }
295 :
296 171060 : static int needsMapping (Key * testKey, Key * errorKey)
297 : {
298 171060 : elektraNamespace ns = keyGetNamespace (errorKey);
299 :
300 171060 : if (ns == KEY_NS_NONE) return 1; // for unit tests
301 170852 : if (ns == KEY_NS_EMPTY) return 1; // for default backend
302 27844 : if (ns == KEY_NS_CASCADING) return 1; // init all namespaces for cascading
303 :
304 15924 : return ns == keyGetNamespace (testKey); // otherwise only init if same ns
305 : }
306 :
307 42765 : static int mapFilesForNamespaces (resolverHandles * p, Key * errorKey)
308 : {
309 42765 : Key * testKey = keyNew ("", KEY_END);
310 : // switch is only present to forget no namespace and to get
311 : // a warning whenever a new namespace is present.
312 : // In fact its linear code executed:
313 42765 : ElektraResolved * resolved = NULL;
314 : switch (KEY_NS_SPEC)
315 : {
316 : case KEY_NS_SPEC:
317 42765 : keySetName (testKey, "spec");
318 42765 : if (needsMapping (testKey, errorKey))
319 : {
320 40093 : if ((resolved = ELEKTRA_PLUGIN_FUNCTION (filename) (KEY_NS_SPEC, (p->spec).path, ELEKTRA_RESOLVER_TEMPFILE_SAMEDIR,
321 : errorKey)) == NULL)
322 : {
323 0 : resolverClose (p);
324 0 : keyDel (testKey);
325 0 : ELEKTRA_SET_RESOURCE_ERROR (errorKey, "Could not resolve filename. Could not resolve spec key");
326 0 : return -1;
327 : }
328 : else
329 : {
330 40093 : p->spec.tempfile = elektraStrDup (resolved->tmpFile);
331 40093 : p->spec.filename = elektraStrDup (resolved->fullPath);
332 40093 : p->spec.dirname = elektraStrDup (resolved->dirname);
333 40093 : ELEKTRA_PLUGIN_FUNCTION (freeHandle) (resolved);
334 : }
335 : }
336 : // FALLTHROUGH
337 :
338 : case KEY_NS_DIR:
339 42765 : keySetName (testKey, "dir");
340 42765 : if (needsMapping (testKey, errorKey))
341 : {
342 38870 : if ((resolved = ELEKTRA_PLUGIN_FUNCTION (filename) (KEY_NS_DIR, (p->dir).path, ELEKTRA_RESOLVER_TEMPFILE_SAMEDIR,
343 : errorKey)) == NULL)
344 : {
345 0 : resolverClose (p);
346 0 : keyDel (testKey);
347 0 : ELEKTRA_SET_RESOURCE_ERROR (errorKey, "Could not resolve filename. Could not resolve dir key");
348 0 : return -1;
349 : }
350 : else
351 : {
352 38870 : p->dir.tempfile = elektraStrDup (resolved->tmpFile);
353 38870 : p->dir.filename = elektraStrDup (resolved->fullPath);
354 38870 : p->dir.dirname = elektraStrDup (resolved->dirname);
355 38870 : ELEKTRA_PLUGIN_FUNCTION (freeHandle) (resolved);
356 : }
357 : }
358 : // FALLTHROUGH
359 : case KEY_NS_USER:
360 42765 : keySetName (testKey, "user");
361 42765 : if (needsMapping (testKey, errorKey))
362 : {
363 40458 : if ((resolved = ELEKTRA_PLUGIN_FUNCTION (filename) (KEY_NS_USER, (p->user).path, ELEKTRA_RESOLVER_TEMPFILE_SAMEDIR,
364 : errorKey)) == NULL)
365 : {
366 0 : resolverClose (p);
367 0 : keyDel (testKey);
368 0 : ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "Could not resolve user key with configuration %s",
369 : ELEKTRA_VARIANT_USER);
370 0 : return -1;
371 : }
372 : else
373 : {
374 40458 : p->user.tempfile = elektraStrDup (resolved->tmpFile);
375 40458 : p->user.filename = elektraStrDup (resolved->fullPath);
376 40458 : p->user.dirname = elektraStrDup (resolved->dirname);
377 40458 : ELEKTRA_PLUGIN_FUNCTION (freeHandle) (resolved);
378 : }
379 : }
380 : // FALLTHROUGH
381 : case KEY_NS_SYSTEM:
382 42765 : keySetName (testKey, "system");
383 42765 : if (needsMapping (testKey, errorKey))
384 : {
385 39696 : if ((resolved = ELEKTRA_PLUGIN_FUNCTION (filename) (KEY_NS_SYSTEM, (p->system).path,
386 : ELEKTRA_RESOLVER_TEMPFILE_SAMEDIR, errorKey)) == NULL)
387 : {
388 0 : resolverClose (p);
389 0 : keyDel (testKey);
390 0 : ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "Could not resolve system key with configuration %s",
391 : ELEKTRA_VARIANT_SYSTEM);
392 0 : return -1;
393 : }
394 : else
395 : {
396 39696 : p->system.tempfile = elektraStrDup (resolved->tmpFile);
397 39696 : p->system.filename = elektraStrDup (resolved->fullPath);
398 39696 : p->system.dirname = elektraStrDup (resolved->dirname);
399 39696 : ELEKTRA_PLUGIN_FUNCTION (freeHandle) (resolved);
400 : }
401 : }
402 : // FALLTHROUGH
403 : case KEY_NS_PROC:
404 : case KEY_NS_EMPTY:
405 : case KEY_NS_NONE:
406 : case KEY_NS_META:
407 : case KEY_NS_CASCADING:
408 : break;
409 : }
410 42765 : keyDel (testKey);
411 42765 : return 0;
412 : }
413 :
414 : /**
415 : * @brief Generate key name for the cache
416 : *
417 : * @param filename the name of the config file
418 : * @ret pointer to the generated key name
419 : */
420 17734 : static char * elektraCacheKeyName (char * filename)
421 : {
422 17734 : char * name = 0;
423 17734 : size_t len = strlen (KDB_CACHE_PREFIX) + strlen ("/") + strlen (ELEKTRA_PLUGIN_NAME) + strlen (filename) + 1;
424 17734 : name = elektraMalloc (len);
425 17734 : name = strcpy (name, KDB_CACHE_PREFIX);
426 17734 : name = strcat (name, "/");
427 17734 : name = strcat (name, ELEKTRA_PLUGIN_NAME);
428 17734 : name = strcat (name, filename);
429 :
430 : ELEKTRA_LOG_DEBUG ("persistent chid key: %s", name);
431 17734 : return name;
432 : }
433 :
434 57789 : int ELEKTRA_PLUGIN_FUNCTION (open) (Plugin * handle, Key * errorKey)
435 : {
436 57789 : KeySet * resolverConfig = elektraPluginGetConfig (handle);
437 57789 : if (ksLookupByName (resolverConfig, "/module", 0)) return 0;
438 42765 : const char * path = keyString (ksLookupByName (resolverConfig, "/path", 0));
439 :
440 42765 : if (!path)
441 : {
442 0 : ELEKTRA_SET_RESOURCE_ERROR (errorKey, "Could not find file configuration");
443 0 : return -1;
444 : }
445 :
446 42765 : resolverHandles * p = elektraMalloc (sizeof (resolverHandles));
447 85530 : resolverInit (&p->spec, path);
448 85530 : resolverInit (&p->dir, path);
449 85530 : resolverInit (&p->user, path);
450 85530 : resolverInit (&p->system, path);
451 :
452 : #if defined(ELEKTRA_RESOLVER_RECURSIVE_MUTEX_INITIALIZATION)
453 : // PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP is available in glibc only
454 : // so we use another mutex for the initialization of the recursive mutex,
455 : // since this section must be thread safe.
456 : pthread_mutex_lock (&elektraResolverInitMutex);
457 : if (!elektraResolverMutexInitialized)
458 : {
459 : pthread_mutexattr_t mutexAttr;
460 : int mutexError;
461 :
462 : if ((mutexError = pthread_mutexattr_init (&mutexAttr)) != 0)
463 : {
464 : ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "Could not initialize recursive mutex: pthread_mutexattr_init returned %d",
465 : mutexError);
466 : pthread_mutex_unlock (&elektraResolverInitMutex);
467 : return -1;
468 : }
469 : if ((mutexError = pthread_mutexattr_settype (&mutexAttr, PTHREAD_MUTEX_RECURSIVE)) != 0)
470 : {
471 : ELEKTRA_SET_RESOURCE_ERRORF (
472 : errorKey, "Could not initialize recursive mutex: pthread_mutexattr_settype returned %d", mutexError);
473 : pthread_mutex_unlock (&elektraResolverInitMutex);
474 : return -1;
475 : }
476 : if ((mutexError = pthread_mutex_init (&elektraResolverMutex, &mutexAttr)) != 0)
477 : {
478 : ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "Could not initialize recursive mutex: pthread_mutex_init returned %d",
479 : mutexError);
480 : pthread_mutex_unlock (&elektraResolverInitMutex);
481 : return -1;
482 : }
483 : elektraResolverMutexInitialized = 1;
484 : }
485 : pthread_mutex_unlock (&elektraResolverInitMutex);
486 : #endif
487 :
488 : // system and spec files need to be world-readable, otherwise they are
489 : // useless
490 42765 : p->system.filemode = 0644;
491 42765 : p->system.dirmode = 0755;
492 42765 : p->spec.filemode = 0644;
493 42765 : p->spec.dirmode = 0755;
494 :
495 42765 : int ret = mapFilesForNamespaces (p, errorKey);
496 :
497 42765 : if (ret != -1)
498 : {
499 42765 : elektraPluginSetData (handle, p);
500 : }
501 :
502 : return ret;
503 : }
504 :
505 57791 : int ELEKTRA_PLUGIN_FUNCTION (close) (Plugin * handle, Key * errorKey ELEKTRA_UNUSED)
506 : {
507 57791 : resolverHandles * ps = elektraPluginGetData (handle);
508 :
509 57791 : if (ps)
510 : {
511 42765 : resolverClose (ps);
512 42765 : elektraPluginSetData (handle, 0);
513 : }
514 :
515 57791 : return 0; /* success */
516 : }
517 :
518 :
519 65834 : int ELEKTRA_PLUGIN_FUNCTION (get) (Plugin * handle, KeySet * returned, Key * parentKey)
520 : {
521 65834 : Key * root = keyNew ("system/elektra/modules/" ELEKTRA_PLUGIN_NAME, KEY_END);
522 :
523 65834 : if (keyRel (root, parentKey) >= 0)
524 : {
525 4777 : keyDel (root);
526 4777 : KeySet * info =
527 : #include "contract.h"
528 4777 : ksAppend (returned, info);
529 4777 : ksDel (info);
530 4777 : return 1;
531 : }
532 61057 : keyDel (root);
533 :
534 61057 : resolverHandle * pk = elektraGetResolverHandle (handle, parentKey);
535 61057 : keySetString (parentKey, pk->filename);
536 :
537 61057 : int errnoSave = errno;
538 : struct stat buf;
539 :
540 : ELEKTRA_LOG ("stat file %s", pk->filename);
541 : /* Start file IO with stat() */
542 122114 : if (stat (pk->filename, &buf) == -1)
543 : {
544 : // no file, so storage has no job
545 37249 : errno = errnoSave;
546 37249 : pk->isMissing = 1;
547 :
548 : // no file, so no metadata:
549 37249 : pk->mtime.tv_sec = 0;
550 37249 : pk->mtime.tv_nsec = 0;
551 37249 : return 0;
552 : }
553 : else
554 : {
555 : // successful, remember mode, uid and gid
556 23808 : pk->filemode = buf.st_mode;
557 23808 : pk->gid = buf.st_gid;
558 23808 : pk->uid = buf.st_uid;
559 23808 : pk->isMissing = 0;
560 : }
561 :
562 : /* Check if update needed */
563 23808 : if (pk->mtime.tv_sec == ELEKTRA_STAT_SECONDS (buf) && pk->mtime.tv_nsec == ELEKTRA_STAT_NANO_SECONDS (buf))
564 : {
565 : // no update, so storage has no job
566 6074 : errno = errnoSave;
567 6074 : return 0;
568 : }
569 :
570 : /* Check if cache update needed */
571 : KeySet * global;
572 17734 : char * name = 0;
573 :
574 17734 : if ((global = elektraPluginGetGlobalKeySet (handle)) != NULL && ELEKTRA_STAT_NANO_SECONDS (buf) != 0)
575 : {
576 17734 : name = elektraCacheKeyName (pk->filename);
577 :
578 : ELEKTRA_LOG_DEBUG ("global-cache: check cache update needed?");
579 17734 : Key * time = ksLookupByName (global, name, KDB_O_NONE);
580 17734 : if (time && keyGetValueSize (time) == sizeof (struct timespec))
581 : {
582 : struct timespec cached;
583 0 : keyGetBinary (time, &cached, sizeof (struct timespec));
584 0 : if (cached.tv_sec == ELEKTRA_STAT_SECONDS (buf) && cached.tv_nsec == ELEKTRA_STAT_NANO_SECONDS (buf))
585 : {
586 : ELEKTRA_LOG_DEBUG ("global-cache: no update needed, everything is fine");
587 : ELEKTRA_LOG_DEBUG ("cached.tv_sec:\t%ld", cached.tv_sec);
588 : ELEKTRA_LOG_DEBUG ("cached.tv_nsec:\t%ld", cached.tv_nsec);
589 : ELEKTRA_LOG_DEBUG ("buf.tv_sec:\t%ld", ELEKTRA_STAT_SECONDS (buf));
590 : ELEKTRA_LOG_DEBUG ("buf.tv_nsec:\t%ld", ELEKTRA_STAT_NANO_SECONDS (buf));
591 : // update timestamp inside resolver
592 0 : pk->mtime.tv_sec = ELEKTRA_STAT_SECONDS (buf);
593 0 : pk->mtime.tv_nsec = ELEKTRA_STAT_NANO_SECONDS (buf);
594 :
595 0 : if (name) elektraFree (name);
596 0 : errno = errnoSave;
597 0 : return ELEKTRA_PLUGIN_STATUS_CACHE_HIT;
598 : }
599 : }
600 : }
601 :
602 17734 : pk->mtime.tv_sec = ELEKTRA_STAT_SECONDS (buf);
603 17734 : pk->mtime.tv_nsec = ELEKTRA_STAT_NANO_SECONDS (buf);
604 :
605 : /* Persist modification times for cache */
606 17734 : if (global != NULL && ELEKTRA_STAT_NANO_SECONDS (buf) != 0)
607 : {
608 : ELEKTRA_LOG_DEBUG ("global-cache: adding file modufication times");
609 17734 : Key * time = keyNew (name, KEY_BINARY, KEY_SIZE, sizeof (struct timespec), KEY_VALUE, &(pk->mtime), KEY_END);
610 17734 : ksAppendKey (global, time);
611 : }
612 :
613 17734 : if (name) elektraFree (name);
614 17734 : errno = errnoSave;
615 17734 : return 1;
616 : }
617 :
618 :
619 : /**
620 : * @brief Open a file and yield an error on conflicts
621 : *
622 : * @param pk->filename will be used
623 : * @param parentKey to yield the error to
624 : *
625 : * @retval 0 on success (might be an error for creating a missing file)
626 : * @retval -1 on conflict
627 : */
628 2457 : static int elektraOpenFile (resolverHandle * pk, Key * parentKey)
629 : {
630 2457 : int flags = 0;
631 :
632 2457 : if (pk->isMissing)
633 : {
634 : // it must be created newly, otherwise we have an conflict
635 : flags = O_RDWR | O_CREAT | O_EXCL;
636 :
637 : // only works when using NFSv3 or later on kernel 2.6 or later
638 : // TODO: add variant with linkat?
639 : }
640 : else
641 : {
642 : // file was there before, so opening should work!
643 2211 : flags = O_RDWR;
644 : }
645 :
646 2457 : errno = 0;
647 2457 : pk->fd = open (pk->filename, flags, pk->filemode);
648 :
649 2457 : if (!pk->isMissing)
650 : {
651 2211 : if (errno == ENOENT)
652 : {
653 2 : ELEKTRA_SET_INTERNAL_ERRORF (parentKey,
654 : "The configuration file '%s' was there earlier, "
655 : "now it is missing",
656 : pk->filename);
657 2 : return -1;
658 : }
659 2209 : else if (pk->fd == -1)
660 : {
661 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not reopen configuration file '%s' for writing. Reason: %s",
662 : pk->filename, strerror (errno));
663 0 : return -1;
664 : }
665 : // successfully reopened
666 : }
667 : else
668 : {
669 246 : if (pk->fd != -1)
670 : {
671 : // successfully created a file
672 233 : pk->removalNeeded = 1;
673 233 : return 0;
674 : }
675 13 : else if (errno == EEXIST)
676 : {
677 10 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey,
678 : "No configuration file was there earlier. "
679 : "Now configuration file '%s' exists",
680 : pk->filename);
681 10 : return -1;
682 : }
683 :
684 : // ignore errors for attempts to create a new file, we will try it again later
685 : }
686 :
687 2212 : errno = 0;
688 :
689 2212 : return 0;
690 : }
691 :
692 :
693 : /**
694 : * @brief Create a file and yield an error if it did not work
695 : *
696 : * @param pk->filename will be used
697 : * @param parentKey to yield the error to
698 : *
699 : * @retval 0 on success
700 : * @retval -1 on error
701 : */
702 3 : static int elektraCreateFile (resolverHandle * pk, Key * parentKey)
703 : {
704 3 : pk->fd = open (pk->filename, O_RDWR | O_CREAT, pk->filemode);
705 :
706 3 : if (pk->fd == -1)
707 : {
708 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not create configuration file '%s'. Reason: %s", pk->filename,
709 : strerror (errno));
710 0 : return -1;
711 : }
712 : return 0;
713 : }
714 :
715 :
716 : /**
717 : * @brief Create pathname recursively.
718 : *
719 : * Try unless the whole path was
720 : * created or it is sure that it cannot be done.
721 : *
722 : * @param pathname The path to create.
723 : *
724 : * @retval 0 on success
725 : * @retval -1 on error + elektra error will be set
726 : */
727 3 : static int elektraMkdirParents (resolverHandle * pk, const char * pathname, Key * parentKey)
728 : {
729 3 : if (mkdir (pathname, pk->dirmode) == -1)
730 : {
731 0 : if (errno != ENOENT)
732 : {
733 : // hopeless, give it up
734 : goto error;
735 : }
736 :
737 : // last part of filename component (basename)
738 0 : char * p = strrchr (pathname, '/');
739 :
740 : /* nothing found */
741 0 : if (p == NULL)
742 : {
743 : // set any errno, corrected in
744 : // elektraAddErrnoText
745 0 : errno = E2BIG;
746 0 : goto error;
747 : }
748 :
749 : /* absolute path */
750 0 : if (p == pathname)
751 : {
752 : // set any errno, corrected in
753 : // elektraAddErrnoText
754 0 : errno = EINVAL;
755 0 : goto error;
756 : }
757 :
758 : /* Cut path at last /. */
759 0 : *p = 0;
760 :
761 : /* Now call ourselves recursively */
762 0 : if (elektraMkdirParents (pk, pathname, parentKey) == -1)
763 : {
764 : // do not yield an error, was already done
765 : // before
766 0 : *p = '/';
767 0 : return -1;
768 : }
769 :
770 : /* Restore path. */
771 0 : *p = '/';
772 :
773 0 : if (mkdir (pathname, pk->dirmode) == -1)
774 : {
775 : goto error;
776 : }
777 : }
778 :
779 : return 0;
780 :
781 : error:
782 : {
783 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey,
784 : "Could not create directory '%s'. Reason: %s. Identity: uid: %u, euid: %u, gid: %u, egid: %u",
785 : pathname, elektraAddErrnoText (), getuid (), geteuid (), getgid (), getegid ());
786 0 : return -1;
787 : }
788 : }
789 :
790 : /**
791 : * @brief Check conflict for the current open file
792 : *
793 : * Does an fstat and checks if mtime are equal as they were
794 : *
795 : * @param pk to get mtime and fd from
796 : * @param parentKey to write errors&warnings to
797 : *
798 : * @retval 0 success
799 : * @retval -1 error
800 : */
801 2445 : static int elektraCheckConflict (resolverHandle * pk, Key * parentKey)
802 : {
803 2445 : if (pk->isMissing)
804 : {
805 : // conflict already handled at file creation time, so just return successfully
806 : return 0;
807 : }
808 :
809 : struct stat buf;
810 :
811 4418 : if (fstat (pk->fd, &buf) == -1)
812 : {
813 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (
814 : parentKey,
815 : "Could not 'fstat' to check for conflict '%s'. Reason: %s. Identity: uid: %u, euid: %u, gid: %u, egid: %u",
816 : pk->filename, elektraAddErrnoText (), getuid (), geteuid (), getgid (), getegid ());
817 :
818 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Assuming conflict because of failed stat (warning %s for details)",
819 : ELEKTRA_ERROR_RESOURCE);
820 0 : return -1;
821 : }
822 :
823 2209 : if (ELEKTRA_STAT_SECONDS (buf) != pk->mtime.tv_sec || ELEKTRA_STAT_NANO_SECONDS (buf) != pk->mtime.tv_nsec)
824 : {
825 6 : ELEKTRA_SET_CONFLICTING_STATE_ERRORF (
826 : parentKey,
827 : "Conflict, file modification time stamp '%ld.%ld' is different than our time stamp '%ld.%ld', config file "
828 : "name is '%s'. "
829 : "Our identity is uid: %u, euid: %u, gid: %u, egid: %u",
830 : ELEKTRA_STAT_SECONDS (buf), ELEKTRA_STAT_NANO_SECONDS (buf), pk->mtime.tv_sec, pk->mtime.tv_nsec, pk->filename,
831 : getuid (), geteuid (), getgid (), getegid ());
832 6 : return -1;
833 : }
834 :
835 :
836 : return 0;
837 : }
838 :
839 : /**
840 : * @brief Does everything needed before the storage plugin will be
841 : * invoked.
842 : *
843 : * @param pk resolver information
844 : * @param parentKey parent
845 : *
846 : * @retval 0 on success
847 : * @retval -1 on error
848 : */
849 2457 : static int elektraSetPrepare (resolverHandle * pk, Key * parentKey)
850 : {
851 2457 : pk->removalNeeded = 0;
852 :
853 2457 : if (elektraOpenFile (pk, parentKey) == -1)
854 : {
855 : // file/none-file conflict OR error on previously existing file
856 : return -1;
857 : }
858 :
859 2445 : if (pk->fd == -1)
860 : {
861 : // try creation of underlying directory
862 3 : elektraMkdirParents (pk, pk->dirname, parentKey);
863 :
864 : // now try to create file
865 3 : if (elektraCreateFile (pk, parentKey) == -1)
866 : {
867 : // no way to be successful
868 : return -1;
869 : }
870 :
871 : // the file was created by us, so we need to remove it
872 : // on error:
873 3 : pk->removalNeeded = 1;
874 : }
875 :
876 2445 : if (elektraLockMutex (parentKey) != 0)
877 : {
878 0 : elektraCloseFile (pk->fd, parentKey);
879 0 : pk->fd = -1;
880 0 : return -1;
881 : }
882 :
883 : // now we have a file, so lock immediately
884 2445 : if (elektraLockFile (pk->fd, parentKey) == -1)
885 : {
886 0 : elektraCloseFile (pk->fd, parentKey);
887 0 : elektraUnlockMutex (parentKey);
888 0 : pk->fd = -1;
889 0 : return -1;
890 : }
891 :
892 2445 : if (elektraCheckConflict (pk, parentKey) == -1)
893 : {
894 6 : elektraUnlockFile (pk->fd, parentKey);
895 6 : elektraCloseFile (pk->fd, parentKey);
896 6 : elektraUnlockMutex (parentKey);
897 6 : pk->fd = -1;
898 6 : return -1;
899 : }
900 :
901 : return 0;
902 : }
903 :
904 0 : static void elektraModifyFileTime (resolverHandle * pk)
905 : {
906 : #ifdef HAVE_CLOCK_GETTIME
907 : // for linux let us calculate a new ns timestamp to use
908 : struct timespec ts;
909 0 : clock_gettime (CLOCK_MONOTONIC, &ts);
910 :
911 0 : if (ts.tv_sec == pk->mtime.tv_sec)
912 : {
913 : // for filesystems not supporting subseconds, make sure the second is changed, too
914 0 : pk->mtime.tv_sec += pk->timeFix;
915 0 : pk->timeFix *= -1; // toggle timefix
916 : }
917 : else
918 : {
919 0 : pk->mtime.tv_sec = ts.tv_sec;
920 : }
921 :
922 0 : if (ts.tv_nsec == pk->mtime.tv_nsec)
923 : {
924 : // also slightly change nsec (same direction as seconds):
925 0 : pk->mtime.tv_nsec += pk->timeFix;
926 : }
927 : else
928 : {
929 0 : pk->mtime.tv_nsec = ts.tv_nsec;
930 : }
931 : #else
932 : // otherwise use simple time toggling schema of seconds
933 : pk->mtime.tv_sec += pk->timeFix;
934 : pk->timeFix *= -1; // toggle timefix
935 : #endif
936 0 : }
937 :
938 :
939 : /* Update timestamp of old file to provoke conflicts in
940 : * stalling processes that might still wait with the old
941 : * filedescriptor */
942 2371 : static void elektraUpdateFileTime (resolverHandle * pk, int fd, Key * parentKey)
943 : {
944 : #ifdef HAVE_FUTIMENS
945 2371 : const struct timespec times[2] = { pk->mtime, // atime
946 : pk->mtime }; // mtime
947 :
948 2371 : if (futimens (fd, times) == -1)
949 : {
950 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Could not update time stamp of '%s'. Reason: %s",
951 : fd == pk->fd ? pk->filename : pk->tempfile, strerror (errno));
952 : }
953 : #elif defined(HAVE_FUTIMES)
954 : const struct timeval times[2] = { { pk->mtime.tv_sec, pk->mtime.tv_nsec / 1000 }, // atime
955 : { pk->mtime.tv_sec, pk->mtime.tv_nsec / 1000 } }; // mtime
956 :
957 : if (futimes (fd, times) == -1)
958 : {
959 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Could not update time stamp of \"%s\", because %s",
960 : fd == pk->fd ? pk->filename : pk->tempfile, strerror (errno));
961 : }
962 : #else
963 : #warning futimens/futimes not defined
964 : #endif
965 2371 : }
966 :
967 : /**
968 : * @brief Now commit the temporary file to be final
969 : *
970 : * @param pk
971 : * @param parentKey
972 : *
973 : * It will also reset pk->fd
974 : *
975 : * @retval 0 on success
976 : * @retval -1 on error
977 : */
978 2371 : static int elektraSetCommit (resolverHandle * pk, Key * parentKey)
979 : {
980 2371 : int ret = 0;
981 :
982 2371 : int fd = open (pk->tempfile, O_RDWR);
983 2371 : if (fd == -1)
984 : {
985 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not open file '%s' again for changing metadata. Reason: %s", pk->tempfile,
986 : strerror (errno));
987 0 : ret = -1;
988 : }
989 :
990 2371 : elektraLockFile (fd, parentKey);
991 :
992 2371 : if (rename (pk->tempfile, pk->filename) == -1)
993 : {
994 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not rename file '%s'. Reason: %s", pk->tempfile, strerror (errno));
995 0 : ret = -1;
996 : }
997 :
998 : ELEKTRA_LOG_DEBUG ("old.tv_sec:\t%ld", pk->mtime.tv_sec);
999 : ELEKTRA_LOG_DEBUG ("old.tv_nsec:\t%ld", pk->mtime.tv_nsec);
1000 : struct stat buf;
1001 2371 : if (fstat (fd, &buf) == -1)
1002 : {
1003 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to stat file '%s'. Reason: %s", pk->tempfile, strerror (errno));
1004 : }
1005 : else
1006 : {
1007 2371 : if (!(pk->mtime.tv_sec == ELEKTRA_STAT_SECONDS (buf) && pk->mtime.tv_nsec == ELEKTRA_STAT_NANO_SECONDS (buf)))
1008 : {
1009 : /* Update timestamp */
1010 2371 : pk->mtime.tv_sec = ELEKTRA_STAT_SECONDS (buf);
1011 2371 : pk->mtime.tv_nsec = ELEKTRA_STAT_NANO_SECONDS (buf);
1012 : }
1013 : else
1014 : {
1015 0 : elektraModifyFileTime (pk);
1016 : // update file visible in filesystem:
1017 0 : elektraUpdateFileTime (pk, fd, parentKey);
1018 :
1019 : /* @post
1020 : For timejump backwards or time not changed,
1021 : use time + 1ns
1022 : This is needed to fulfill the postcondition
1023 : that the timestamp changed at least slightly
1024 : and makes sure that all processes that stat()ed
1025 : the file will get a conflict. */
1026 : }
1027 : }
1028 :
1029 2371 : elektraUpdateFileTime (pk, pk->fd, parentKey);
1030 : ELEKTRA_LOG_DEBUG ("new.tv_sec:\t%ld", pk->mtime.tv_sec);
1031 : ELEKTRA_LOG_DEBUG ("new.tv_nsec:\t%ld", pk->mtime.tv_nsec);
1032 :
1033 2371 : if (buf.st_mode != pk->filemode)
1034 : {
1035 : // change mode to what it was before
1036 1291 : if (fchmod (fd, pk->filemode) == -1)
1037 : {
1038 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey,
1039 : "Could not change permissions of temporary file '%s' from '%o' to '%o'. Reason: %s",
1040 : pk->tempfile, buf.st_mode, pk->filemode, strerror (errno));
1041 : }
1042 : }
1043 :
1044 2371 : if (!pk->isMissing && (buf.st_uid != pk->uid || buf.st_gid != pk->gid))
1045 : {
1046 48 : if (fchown (fd, pk->uid, pk->gid) == -1)
1047 : {
1048 48 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey,
1049 : "Could not change owner of temporary file '%s' from %d.%d to %d.%d. Reason: %s",
1050 : pk->tempfile, buf.st_uid, buf.st_gid, pk->uid, pk->gid, strerror (errno));
1051 : }
1052 : }
1053 :
1054 : // file is present now!
1055 2371 : pk->isMissing = 0;
1056 :
1057 2371 : DIR * dirp = opendir (pk->dirname);
1058 : // checking dirp not needed, fsync will have EBADF
1059 2371 : if (fsync (dirfd (dirp)) == -1)
1060 : {
1061 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Could not sync directory '%s'. Reason: %s", pk->dirname, strerror (errno));
1062 : }
1063 2371 : closedir (dirp);
1064 :
1065 2371 : elektraUnlockFile (pk->fd, parentKey);
1066 2371 : elektraCloseFile (pk->fd, parentKey);
1067 2371 : elektraUnlockFile (fd, parentKey);
1068 2371 : elektraCloseFile (fd, parentKey);
1069 2371 : elektraUnlockMutex (parentKey);
1070 :
1071 2371 : return ret;
1072 : }
1073 :
1074 :
1075 5209 : int ELEKTRA_PLUGIN_FUNCTION (set) (Plugin * handle, KeySet * ks, Key * parentKey)
1076 : {
1077 5209 : resolverHandle * pk = elektraGetResolverHandle (handle, parentKey);
1078 :
1079 5209 : int errnoSave = errno;
1080 5209 : int ret = 1;
1081 :
1082 : ELEKTRA_LOG ("entering resolver::set %d \"%s\"", pk->fd, pk->filename);
1083 5209 : if (pk->fd == -1)
1084 : {
1085 : // no fd up to now, so we are in first phase
1086 :
1087 : // we operate on the tmp file
1088 2649 : keySetString (parentKey, pk->tempfile);
1089 :
1090 2649 : if (ksGetSize (ks) == 0)
1091 : {
1092 192 : ret = 0;
1093 :
1094 : ELEKTRA_LOG ("check if removal of the configuration file \"%s\" would work later", pk->filename);
1095 192 : if (access (pk->dirname, W_OK | X_OK) == -1)
1096 : {
1097 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not remove file '%s'. Reason: %s", pk->filename,
1098 : strerror (errno));
1099 0 : ret = -1;
1100 : }
1101 :
1102 : // remove file on commit
1103 192 : pk->fd = -2;
1104 : }
1105 : else
1106 : {
1107 : // prepare phase
1108 2457 : if (elektraSetPrepare (pk, parentKey) == -1)
1109 : {
1110 18 : ret = -1;
1111 : }
1112 : }
1113 : }
1114 2560 : else if (pk->fd == -2)
1115 : {
1116 : ELEKTRA_LOG ("unlink configuration file \"%s\"", pk->filename);
1117 189 : if (unlink (pk->filename) == -1)
1118 : {
1119 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not remove file '%s'. Reason: %s", pk->filename, strerror (errno));
1120 0 : ret = -1;
1121 : }
1122 :
1123 : // reset for the next time
1124 189 : pk->fd = -1;
1125 : }
1126 : else
1127 : {
1128 : // now we do not operate on the temporary file anymore,
1129 : // but on the real file
1130 2371 : keySetString (parentKey, pk->filename);
1131 :
1132 : /* we have an fd, so we are in second phase*/
1133 2371 : if (elektraSetCommit (pk, parentKey) == -1)
1134 : {
1135 0 : ret = -1;
1136 : }
1137 :
1138 : // reset for next time
1139 2371 : pk->fd = -1;
1140 : }
1141 :
1142 : ELEKTRA_LOG ("leaving resolver::set %d \"%s\"", pk->fd, pk->filename);
1143 :
1144 5209 : errno = errnoSave; // maybe some temporary error happened
1145 :
1146 5209 : return ret;
1147 : }
1148 :
1149 111 : static void elektraUnlinkFile (char * filename, Key * parentKey)
1150 : {
1151 111 : int errnoSave = errno;
1152 111 : if (unlink (filename) == -1)
1153 : {
1154 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Could not unlink the file '%s'. Reason: %s", filename, strerror (errno));
1155 0 : errno = errnoSave;
1156 : }
1157 111 : }
1158 :
1159 89 : int ELEKTRA_PLUGIN_FUNCTION (error) (Plugin * handle, KeySet * r ELEKTRA_UNUSED, Key * parentKey)
1160 : {
1161 89 : resolverHandle * pk = elektraGetResolverHandle (handle, parentKey);
1162 :
1163 89 : if (pk->fd == -2)
1164 : { // removal aborted state (= empty keyset, but error)
1165 : // reset for next time
1166 3 : pk->fd = -1;
1167 3 : return 0;
1168 : }
1169 :
1170 86 : elektraUnlinkFile (pk->tempfile, parentKey);
1171 :
1172 86 : if (pk->fd > -1)
1173 : { // with fd
1174 68 : elektraUnlockFile (pk->fd, parentKey);
1175 68 : elektraCloseFile (pk->fd, parentKey);
1176 68 : if (pk->removalNeeded == 1)
1177 : { // removal needed state (= resolver created file, but error)
1178 25 : elektraUnlinkFile (pk->filename, parentKey);
1179 : }
1180 68 : elektraUnlockMutex (parentKey);
1181 : }
1182 :
1183 : // reset for next time
1184 86 : pk->fd = -1;
1185 :
1186 86 : return 0;
1187 : }
1188 :
1189 2559 : int ELEKTRA_PLUGIN_FUNCTION (commit) (Plugin * handle, KeySet * returned, Key * parentKey)
1190 : {
1191 2559 : return ELEKTRA_PLUGIN_FUNCTION (set) (handle, returned, parentKey);
1192 : }
1193 :
1194 :
1195 57787 : Plugin * ELEKTRA_PLUGIN_EXPORT
1196 : {
1197 : // clang-format off
1198 57787 : return elektraPluginExport(ELEKTRA_PLUGIN_NAME,
1199 : ELEKTRA_PLUGIN_OPEN, &ELEKTRA_PLUGIN_FUNCTION(open),
1200 : ELEKTRA_PLUGIN_CLOSE, &ELEKTRA_PLUGIN_FUNCTION(close),
1201 : ELEKTRA_PLUGIN_GET, &ELEKTRA_PLUGIN_FUNCTION(get),
1202 : ELEKTRA_PLUGIN_SET, &ELEKTRA_PLUGIN_FUNCTION(set),
1203 : ELEKTRA_PLUGIN_ERROR, &ELEKTRA_PLUGIN_FUNCTION(error),
1204 : ELEKTRA_PLUGIN_COMMIT, &ELEKTRA_PLUGIN_FUNCTION (commit),
1205 : ELEKTRA_PLUGIN_END);
1206 : }
1207 :
|