1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <platform.h>
26 #include <file_lib.h>
27 #include <misc_lib.h>
28 #include <dir.h>
29 #include <logging.h>
30
31 #include <alloc.h>
32 #include <definitions.h> /* CF_PERMS_DEFAULT */
33 #include <libgen.h>
34 #include <logging.h>
35 #include <string_lib.h> /* memcchr */
36 #include <path.h>
37
38 #ifndef __MINGW32__
39 #include <glob.h>
40 #else
41 #include <windows.h> /* LockFileEx and friends */
42 #endif
43
44 #define SYMLINK_MAX_DEPTH 32
45
FileCanOpen(const char * path,const char * modes)46 bool FileCanOpen(const char *path, const char *modes)
47 {
48 FILE *test = NULL;
49
50 if ((test = fopen(path, modes)) != NULL)
51 {
52 fclose(test);
53 return true;
54 }
55 else
56 {
57 return false;
58 }
59 }
60
61 #define READ_BUFSIZE 4096
62
FileRead(const char * filename,size_t max_size,bool * truncated)63 Writer *FileRead(const char *filename, size_t max_size, bool *truncated)
64 {
65 int fd = safe_open(filename, O_RDONLY);
66 if (fd == -1)
67 {
68 return NULL;
69 }
70
71 Writer *w = FileReadFromFd(fd, max_size, truncated);
72 close(fd);
73 return w;
74 }
75
ReadFileStreamToBuffer(FILE * file,size_t max_bytes,char * buf)76 ssize_t ReadFileStreamToBuffer(FILE *file, size_t max_bytes, char *buf)
77 {
78 size_t bytes_read = 0;
79 size_t n = 0;
80 while (bytes_read < max_bytes)
81 {
82 n = fread(buf + bytes_read, 1, max_bytes - bytes_read, file);
83 if (ferror(file) && !feof(file))
84 {
85 return FILE_ERROR_READ;
86 }
87 else if (n == 0)
88 {
89 break;
90 }
91 bytes_read += n;
92 }
93 if (ferror(file))
94 {
95 return FILE_ERROR_READ;
96 }
97 return bytes_read;
98 }
99
File_Copy(const char * src,const char * dst)100 bool File_Copy(const char *src, const char *dst)
101 {
102 assert(src != NULL);
103 assert(dst != NULL);
104
105 Log(LOG_LEVEL_INFO, "Copying: '%s' -> '%s'", src, dst);
106
107 FILE *in = safe_fopen(src, "r");
108 if (in == NULL)
109 {
110 Log(LOG_LEVEL_ERR, "Could not open '%s' (%s)", src, strerror(errno));
111 return false;
112 }
113
114 FILE *out = safe_fopen_create_perms(dst, "w", CF_PERMS_DEFAULT);
115 if (out == NULL)
116 {
117 Log(LOG_LEVEL_ERR, "Could not open '%s' (%s)", dst, strerror(errno));
118 fclose(in);
119 return false;
120 }
121
122 size_t bytes_in = 0;
123 size_t bytes_out = 0;
124 bool ret = true;
125 do
126 {
127 #define BUFSIZE 1024
128 char buf[BUFSIZE] = {0};
129
130 bytes_in = fread(buf, sizeof(char), sizeof(buf), in);
131 bytes_out = fwrite(buf, sizeof(char), bytes_in, out);
132 while (bytes_out < bytes_in && !ferror(out))
133 {
134 bytes_out += fwrite(
135 buf + bytes_out, sizeof(char), bytes_in - bytes_out, out);
136 }
137 } while (!feof(in) && !ferror(in) && !ferror(out) &&
138 bytes_in == bytes_out);
139
140 if (ferror(in))
141 {
142 Log(LOG_LEVEL_ERR, "Error encountered while reading '%s'", src);
143 ret = false;
144 }
145 else if (ferror(out))
146 {
147 Log(LOG_LEVEL_ERR, "Error encountered while writing '%s'", dst);
148 ret = false;
149 }
150 else if (bytes_in != bytes_out)
151 {
152 Log(LOG_LEVEL_ERR, "Did not copy the whole file");
153 ret = false;
154 }
155
156 const int i = fclose(in);
157 if (i != 0)
158 {
159 Log(LOG_LEVEL_ERR,
160 "Error encountered while closing '%s' (%s)",
161 src,
162 strerror(errno));
163 ret = false;
164 }
165 const int o = fclose(out);
166 if (o != 0)
167 {
168 Log(LOG_LEVEL_ERR,
169 "Error encountered while closing '%s' (%s)",
170 dst,
171 strerror(errno));
172 ret = false;
173 }
174 return ret;
175 }
176
File_CopyToDir(const char * src,const char * dst_dir)177 bool File_CopyToDir(const char *src, const char *dst_dir)
178 {
179 assert(src != NULL);
180 assert(dst_dir != NULL);
181 assert(StringEndsWith(dst_dir, FILE_SEPARATOR_STR));
182
183 const char *filename = Path_Basename(src);
184 if (filename == NULL)
185 {
186 Log(LOG_LEVEL_ERR, "Cannot find filename in '%s'", src);
187 return false;
188 }
189
190 char dst[PATH_MAX] = {0};
191 const int s = snprintf(dst, PATH_MAX, "%s%s", dst_dir, filename);
192 if (s >= PATH_MAX)
193 {
194 Log(LOG_LEVEL_ERR, "Copy destination path too long: '%s...'", dst);
195 return false;
196 }
197
198 if (!File_Copy(src, dst))
199 {
200 Log(LOG_LEVEL_ERR, "Copying '%s' failed", filename);
201 return false;
202 }
203
204 return true;
205 }
206
FileReadFromFd(int fd,size_t max_size,bool * truncated)207 Writer *FileReadFromFd(int fd, size_t max_size, bool *truncated)
208 {
209 if (truncated)
210 {
211 *truncated = false;
212 }
213
214 Writer *w = StringWriter();
215 for (;;)
216 {
217 char buf[READ_BUFSIZE];
218 /* Reading more data than needed is deliberate. It is a truncation detection. */
219 ssize_t read_ = read(fd, buf, READ_BUFSIZE);
220
221 if (read_ == 0)
222 {
223 /* Done. */
224 return w;
225 }
226 else if (read_ < 0)
227 {
228 if (errno != EINTR)
229 {
230 /* Something went wrong. */
231 WriterClose(w);
232 return NULL;
233 }
234 /* Else: interrupted - try again. */
235 }
236 else if (read_ + StringWriterLength(w) > max_size)
237 {
238 WriterWriteLen(w, buf, max_size - StringWriterLength(w));
239 /* Reached limit - stop. */
240 if (truncated)
241 {
242 *truncated = true;
243 }
244 return w;
245 }
246 else /* Filled buffer; copy and ask for more. */
247 {
248 WriterWriteLen(w, buf, read_);
249 }
250 }
251 }
252
FullWrite(int desc,const char * ptr,size_t len)253 ssize_t FullWrite(int desc, const char *ptr, size_t len)
254 {
255 ssize_t total_written = 0;
256
257 while (len > 0)
258 {
259 int written = write(desc, ptr, len);
260
261 if (written < 0)
262 {
263 if (errno == EINTR)
264 {
265 continue;
266 }
267
268 return written;
269 }
270
271 total_written += written;
272 ptr += written;
273 len -= written;
274 }
275
276 return total_written;
277 }
278
FullRead(int fd,char * ptr,size_t len)279 ssize_t FullRead(int fd, char *ptr, size_t len)
280 {
281 ssize_t total_read = 0;
282
283 while (len > 0)
284 {
285 ssize_t bytes_read = read(fd, ptr, len);
286
287 if (bytes_read < 0)
288 {
289 if (errno == EINTR)
290 {
291 continue;
292 }
293
294 return -1;
295 }
296
297 if (bytes_read == 0)
298 {
299 return total_read;
300 }
301
302 total_read += bytes_read;
303 ptr += bytes_read;
304 len -= bytes_read;
305 }
306
307 return total_read;
308 }
309
310 /**
311 * @note difference with files_names.h:IsDir() is that this doesn't
312 * follow symlinks, so a symlink is never a directory...
313 */
IsDirReal(const char * path)314 bool IsDirReal(const char *path)
315 {
316 struct stat s;
317
318 if (lstat(path, &s) == -1)
319 {
320 return false; // Error
321 }
322
323 return (S_ISDIR(s.st_mode) != 0);
324 }
325
326 #ifndef __MINGW32__
FileNewLineMode(ARG_UNUSED const char * file)327 NewLineMode FileNewLineMode(ARG_UNUSED const char *file)
328 {
329 return NewLineMode_Unix;
330 }
331 #endif // !__MINGW32__
332
IsAbsoluteFileName(const char * f)333 bool IsAbsoluteFileName(const char *f)
334 {
335 int off = 0;
336
337 // Check for quoted strings
338
339 for (off = 0; f[off] == '\"'; off++)
340 {
341 }
342
343 #ifdef _WIN32
344 if (IsFileSep(f[off]) && IsFileSep(f[off + 1]))
345 {
346 return true;
347 }
348
349 if (isalpha(f[off]) && f[off + 1] == ':' && IsFileSep(f[off + 2]))
350 {
351 return true;
352 }
353 #endif
354 if (IsFileSep(f[off]))
355 {
356 return true;
357 }
358
359 return false;
360 }
361
362 /* We assume that s is at least MAX_FILENAME large.
363 * MapName() is thread-safe, but the argument is modified. */
364
365 #ifdef _WIN32
366 # if defined(__MINGW32__)
367
MapNameCopy(const char * s)368 char *MapNameCopy(const char *s)
369 {
370 char *str = xstrdup(s);
371
372 char *c = str;
373 while ((c = strchr(c, '/')))
374 {
375 *c = '\\';
376 }
377
378 return str;
379 }
380
MapName(char * s)381 char *MapName(char *s)
382 {
383 char *c = s;
384
385 while ((c = strchr(c, '/')))
386 {
387 *c = '\\';
388 }
389 return s;
390 }
391
392 # elif defined(__CYGWIN__)
393
MapNameCopy(const char * s)394 char *MapNameCopy(const char *s)
395 {
396 Writer *w = StringWriter();
397
398 /* c:\a\b -> /cygdrive/c\a\b */
399 if (s[0] && isalpha(s[0]) && s[1] == ':')
400 {
401 WriterWriteF(w, "/cygdrive/%c", s[0]);
402 s += 2;
403 }
404
405 for (; *s; s++)
406 {
407 /* a//b//c -> a/b/c */
408 /* a\\b\\c -> a\b\c */
409 if (IsFileSep(*s) && IsFileSep(*(s + 1)))
410 {
411 continue;
412 }
413
414 /* a\b\c -> a/b/c */
415 WriterWriteChar(w, *s == '\\' ? '/' : *s);
416 }
417
418 return StringWriterClose(w);
419 }
420
MapName(char * s)421 char *MapName(char *s)
422 {
423 char *ret = MapNameCopy(s);
424
425 if (strlcpy(s, ret, MAX_FILENAME) >= MAX_FILENAME)
426 {
427 FatalError(ctx, "Expanded path (%s) is longer than MAX_FILENAME ("
428 TO_STRING(MAX_FILENAME) ") characters",
429 ret);
430 }
431 free(ret);
432
433 return s;
434 }
435
436 # else/* !__MINGW32__ && !__CYGWIN__ */
437 # error Unknown NT-based compilation environment
438 # endif/* __MINGW32__ || __CYGWIN__ */
439 #else /* !_WIN32 */
440
MapName(char * s)441 char *MapName(char *s)
442 {
443 return s;
444 }
445
MapNameCopy(const char * s)446 char *MapNameCopy(const char *s)
447 {
448 return xstrdup(s);
449 }
450
451 #endif /* !_WIN32 */
452
MapNameForward(char * s)453 char *MapNameForward(char *s)
454 /* Like MapName(), but maps all slashes to forward */
455 {
456 while ((s = strchr(s, '\\')))
457 {
458 *s = '/';
459 }
460 return s;
461 }
462
463
464 #ifdef TEST_SYMLINK_ATOMICITY
465 void switch_symlink_hook();
466 #define TEST_SYMLINK_SWITCH_POINT switch_symlink_hook();
467 #else
468 #define TEST_SYMLINK_SWITCH_POINT
469 #endif
470
ListDir(const char * dir,const char * extension)471 Seq *ListDir(const char *dir, const char *extension)
472 {
473 Dir *dirh = DirOpen(dir);
474 if (dirh == NULL)
475 {
476 return NULL;
477 }
478
479 Seq *contents = SeqNew(10, free);
480
481 const struct dirent *dirp;
482
483 while ((dirp = DirRead(dirh)) != NULL)
484 {
485 const char *name = dirp->d_name;
486 if (extension == NULL || StringEndsWithCase(name, extension, true))
487 {
488 SeqAppend(contents, Path_JoinAlloc(dir, name));
489 }
490 }
491 DirClose(dirh);
492
493 return contents;
494 }
495
SetUmask(mode_t new_mask)496 mode_t SetUmask(mode_t new_mask)
497 {
498 const mode_t old_mask = umask(new_mask);
499 Log(LOG_LEVEL_DEBUG, "Set umask to %o, was %o", new_mask, old_mask);
500 return old_mask;
501 }
RestoreUmask(mode_t old_mask)502 void RestoreUmask(mode_t old_mask)
503 {
504 umask(old_mask);
505 Log(LOG_LEVEL_DEBUG, "Restored umask to %o", old_mask);
506 }
507
508 /**
509 * Opens a file safely, with default (strict) permissions on creation.
510 * See safe_open_create_perms for more documentation.
511 *
512 * @param pathname The path to open.
513 * @param flags Same flags as for system open().
514 * @return Same errors as open().
515 */
safe_open(const char * pathname,int flags)516 int safe_open(const char *pathname, int flags)
517 {
518 return safe_open_create_perms(pathname, flags, CF_PERMS_DEFAULT);
519 }
520
521 /**
522 * Opens a file safely. It will follow symlinks, but only if the symlink is
523 * trusted, that is, if the owner of the symlink and the owner of the target are
524 * the same, or if the owner of the symlink is either root or the user running
525 * the current process. All components are checked, even symlinks encountered in
526 * earlier parts of the path name.
527 *
528 * It should always be used when opening a file or directory that is not
529 * guaranteed to be owned by root.
530 *
531 * safe_open and safe_fopen both default to secure (0600) file creation perms.
532 * The _create_perms variants allow you to explicitly set different permissions.
533 *
534 * @param pathname The path to open
535 * @param flags Same flags as for system open()
536 * @param create_perms Permissions for file, only relevant on file creation
537 * @return Same errors as open()
538 * @see safe_fopen_create_perms()
539 * @see safe_open()
540 */
safe_open_create_perms(const char * const pathname,int flags,const mode_t create_perms)541 int safe_open_create_perms(
542 const char *const pathname, int flags, const mode_t create_perms)
543 {
544 if (flags & O_TRUNC)
545 {
546 /* Undefined behaviour otherwise, according to the standard. */
547 assert((flags & O_RDWR) || (flags & O_WRONLY));
548 }
549
550 if (!pathname)
551 {
552 errno = EINVAL;
553 return -1;
554 }
555
556 if (*pathname == '\0')
557 {
558 errno = ENOENT;
559 return -1;
560 }
561
562 #ifdef __MINGW32__
563 // Windows gets off easy. No symlinks there.
564 return open(pathname, flags, create_perms);
565 #elif defined(__ANDROID__)
566 // if effective user is not root then don't try to open
567 // all paths from '/' up, might not have permissions.
568 uid_t p_euid = geteuid();
569 if (p_euid != 0)
570 {
571 return open(pathname, flags, create_perms);
572 }
573 #else // !__MINGW32__ and !__ANDROID__
574 const size_t path_bufsize = strlen(pathname) + 1;
575 char path[path_bufsize];
576 const size_t res_len = StringCopy(pathname, path, path_bufsize);
577 UNUSED(res_len);
578 assert(res_len == strlen(pathname));
579 int currentfd;
580 const char *first_dir;
581 bool trunc = false;
582 const int orig_flags = flags;
583 char *next_component = path;
584 bool p_uid;
585
586 if (*next_component == '/')
587 {
588 first_dir = "/";
589 // Eliminate double slashes.
590 while (*(++next_component) == '/') { /*noop*/ }
591 if (!*next_component)
592 {
593 next_component = NULL;
594 }
595 }
596 else
597 {
598 first_dir = ".";
599 }
600 currentfd = openat(AT_FDCWD, first_dir, O_RDONLY);
601 if (currentfd < 0)
602 {
603 return -1;
604 }
605
606 // current process user id
607 p_uid = geteuid();
608
609 size_t final_size = (size_t) -1;
610 while (next_component)
611 {
612 char *component = next_component;
613 next_component = strchr(component + 1, '/');
614 // Used to restore the slashes in the final path component.
615 char *restore_slash = NULL;
616 if (next_component)
617 {
618 restore_slash = next_component;
619 *next_component = '\0';
620 // Eliminate double slashes.
621 while (*(++next_component) == '/') { /*noop*/ }
622 if (!*next_component)
623 {
624 next_component = NULL;
625 }
626 else
627 {
628 restore_slash = NULL;
629 }
630 }
631
632 // In cases of a race condition when creating a file, our attempt to open it may fail
633 // (see O_EXCL and O_CREAT flags below). However, this can happen even under normal
634 // circumstances, if we are unlucky; it does not mean that someone is up to something bad.
635 // So retry it a few times before giving up.
636 int attempts = 3;
637 trunc = false;
638 while (true)
639 {
640
641 if (( (orig_flags & O_RDWR) || (orig_flags & O_WRONLY))
642 && (orig_flags & O_TRUNC))
643 {
644 trunc = true;
645 /* We need to check after we have opened the file whether we
646 * opened the right one. But if we truncate it, the damage is
647 * already done, we have destroyed the contents, and that file
648 * might have been a symlink to /etc/shadow! So save that flag
649 * and apply the truncation afterwards instead. */
650 flags &= ~O_TRUNC;
651 }
652
653 if (restore_slash)
654 {
655 *restore_slash = '\0';
656 }
657
658 struct stat stat_before, stat_after;
659 int stat_before_result = fstatat(currentfd, component, &stat_before, AT_SYMLINK_NOFOLLOW);
660 if (stat_before_result < 0
661 && (errno != ENOENT
662 || next_component // Meaning "not a leaf".
663 || !(flags & O_CREAT)))
664 {
665 close(currentfd);
666 return -1;
667 }
668
669 /*
670 * This testing entry point is about the following real-world
671 * scenario: There can be an attacker that at this point
672 * overwrites the existing file or writes a file, invalidating
673 * basically the previous fstatat().
674 *
675 * - We make sure that can't happen if the file did not exist, by
676 * creating with O_EXCL.
677 * - We make sure that can't happen if the file existed, by
678 * comparing with fstat() result after the open().
679 *
680 */
681 TEST_SYMLINK_SWITCH_POINT
682
683 if (!next_component) /* last component */
684 {
685 if (stat_before_result < 0)
686 {
687 assert(flags & O_CREAT);
688
689 // Doesn't exist. Make sure *we* create it.
690 flags |= O_EXCL;
691
692 /* No need to ftruncate() the file at the end. */
693 trunc = false;
694 }
695 else
696 {
697 if ((flags & O_CREAT) && (flags & O_EXCL))
698 {
699 close(currentfd);
700 errno = EEXIST;
701 return -1;
702 }
703
704 // Already exists. Make sure we *don't* create it.
705 flags &= ~O_CREAT;
706 }
707 if (restore_slash)
708 {
709 *restore_slash = '/';
710 }
711 int filefd = openat(currentfd, component, flags, create_perms);
712 if (filefd < 0)
713 {
714 if ((stat_before_result < 0 && !(orig_flags & O_EXCL) && errno == EEXIST) ||
715 (stat_before_result >= 0 && (orig_flags & O_CREAT) && errno == ENOENT))
716 {
717 if (--attempts >= 0)
718 {
719 // Might be our fault. Try again.
720 flags = orig_flags;
721 continue;
722 }
723 else
724 {
725 // Too many attempts. Give up.
726 // Most likely a link that is in the way of file creation, but can also
727 // be a file that is constantly created and deleted (race condition).
728 // It is not possible to separate between the two, so return EACCES to
729 // signal that we denied access.
730 errno = EACCES;
731 }
732 }
733 close(currentfd);
734 return -1;
735 }
736 close(currentfd);
737 currentfd = filefd;
738 }
739 else
740 {
741 int new_currentfd = openat(currentfd, component, O_RDONLY);
742 close(currentfd);
743 if (new_currentfd < 0)
744 {
745 return -1;
746 }
747 currentfd = new_currentfd;
748 }
749
750 /* If file did exist, we fstat() again and compare with previous. */
751
752 if (stat_before_result == 0)
753 {
754 if (fstat(currentfd, &stat_after) < 0)
755 {
756 close(currentfd);
757 return -1;
758 }
759 // Some attacks may use symlinks to get higher privileges
760 // The safe cases are:
761 // * symlinks owned by root
762 // * symlinks owned by the user running the process
763 // * symlinks that have the same owner and group as the destination
764 if (stat_before.st_uid != 0 &&
765 stat_before.st_uid != p_uid &&
766 (stat_before.st_uid != stat_after.st_uid || stat_before.st_gid != stat_after.st_gid))
767 {
768 close(currentfd);
769 Log(LOG_LEVEL_ERR, "Cannot follow symlink '%s'; it is not "
770 "owned by root or the user running this process, and "
771 "the target owner and/or group differs from that of "
772 "the symlink itself.", pathname);
773 // Return ENOLINK to signal that the link cannot be followed
774 // ('Link has been severed').
775 errno = ENOLINK;
776 return -1;
777 }
778
779 final_size = (size_t) stat_after.st_size;
780 }
781
782 // If we got here, we've been successful, so don't try again.
783 break;
784 }
785 }
786
787 /* Truncate if O_CREAT and the file preexisted. */
788 if (trunc)
789 {
790 /* Do not truncate if the size is already zero, some
791 * filesystems don't support ftruncate() with offset>=size. */
792 assert(final_size != (size_t) -1);
793
794 if (final_size != 0)
795 {
796 int tr_ret = ftruncate(currentfd, 0);
797 if (tr_ret < 0)
798 {
799 Log(LOG_LEVEL_ERR,
800 "safe_open: unexpected failure (ftruncate: %s)",
801 GetErrorStr());
802 close(currentfd);
803 return -1;
804 }
805 }
806 }
807
808 return currentfd;
809 #endif // !__MINGW32__
810 }
811
safe_fopen(const char * const path,const char * const mode)812 FILE *safe_fopen(const char *const path, const char *const mode)
813 {
814 return safe_fopen_create_perms(path, mode, CF_PERMS_DEFAULT);
815 }
816
817 /**
818 * Opens a file safely. It will follow symlinks, but only if the symlink is trusted,
819 * that is, if the owner of the symlink and the owner of the target are the same,
820 * or if the owner of the symlink is either root or the user running the current process.
821 * All components are checked, even symlinks encountered in earlier parts of the
822 * path name.
823 *
824 * It should always be used when opening a directory that is not guaranteed to be
825 * owned by root.
826 *
827 * @param pathname The path to open.
828 * @param flags Same mode as for system fopen().
829 * @param create_perms Permissions for file, only relevant on file creation.
830 * @return Same errors as fopen().
831 */
safe_fopen_create_perms(const char * const path,const char * const mode,const mode_t create_perms)832 FILE *safe_fopen_create_perms(
833 const char *const path, const char *const mode, const mode_t create_perms)
834 {
835 if (!path || !mode)
836 {
837 errno = EINVAL;
838 return NULL;
839 }
840
841 int flags = 0;
842 for (int c = 0; mode[c]; c++)
843 {
844 switch (mode[c])
845 {
846 case 'r':
847 flags |= O_RDONLY;
848 break;
849 case 'w':
850 flags |= O_WRONLY | O_TRUNC | O_CREAT;
851 break;
852 case 'a':
853 flags |= O_WRONLY | O_CREAT;
854 break;
855 case '+':
856 flags &= ~(O_RDONLY | O_WRONLY);
857 flags |= O_RDWR;
858 break;
859 case 'b':
860 flags |= O_BINARY;
861 break;
862 case 't':
863 flags |= O_TEXT;
864 break;
865 case 'x':
866 flags |= O_EXCL;
867 break;
868 default:
869 ProgrammingError("Invalid flag for fopen: %s", mode);
870 return NULL;
871 }
872 }
873
874 int fd = safe_open_create_perms(path, flags, create_perms);
875 if (fd < 0)
876 {
877 return NULL;
878 }
879 FILE *ret = fdopen(fd, mode);
880 if (!ret)
881 {
882 close(fd);
883 return NULL;
884 }
885
886 if (mode[0] == 'a')
887 {
888 if (fseek(ret, 0, SEEK_END) < 0)
889 {
890 fclose(ret);
891 return NULL;
892 }
893 }
894
895 return ret;
896 }
897
898 /**
899 * Use this instead of chdir(). It changes into the directory safely, using safe_open().
900 * @param path Path to change into.
901 * @return Same return values as chdir().
902 */
safe_chdir(const char * path)903 int safe_chdir(const char *path)
904 {
905 #ifdef __MINGW32__
906 return chdir(path);
907 #else // !__MINGW32__
908 int fd = safe_open(path, O_RDONLY);
909 if (fd < 0)
910 {
911 return -1;
912 }
913 if (fchdir(fd) < 0)
914 {
915 close(fd);
916 return -1;
917 }
918 close(fd);
919 return 0;
920 #endif // !__MINGW32__
921 }
922
923 #ifndef __MINGW32__
924
925 /**
926 * Opens the true parent dir of the file in the path given. The notable
927 * difference from doing it the naive way (open(dirname(path))) is that it
928 * can follow the symlinks of the path, ending up in the true parent dir of the
929 * path. It follows the same safe mechanisms as `safe_open()` to do so. If
930 * AT_SYMLINK_NOFOLLOW is given, it is equivalent to doing it the naive way (but
931 * still following "safe" semantics).
932 * @param path Path to open parent directory of.
933 * @param flags Flags to use for fchownat.
934 * @param link_user If we have traversed a link already, which user was it.
935 * @param link_group If we have traversed a link already, which group was it.
936 * @param traversed_link Whether we have traversed a link. If this is false the
937 * two previus arguments are ignored. This is used enforce
938 * the correct UID/GID combination when following links.
939 * Initially this is false, but will be set to true in
940 * sub invocations if we follow a link.
941 * @param loop_countdown Protection against infinite loop following.
942 * @return File descriptor pointing to the parent directory of path, or -1 on
943 * error.
944 */
safe_open_true_parent_dir(const char * path,int flags,uid_t link_user,gid_t link_group,bool traversed_link,int loop_countdown)945 static int safe_open_true_parent_dir(const char *path,
946 int flags,
947 uid_t link_user,
948 gid_t link_group,
949 bool traversed_link,
950 int loop_countdown)
951 {
952 int dirfd = -1;
953 int ret = -1;
954
955 char *parent_dir_alloc = xstrdup(path);
956 char *leaf_alloc = xstrdup(path);
957 char *parent_dir = dirname(parent_dir_alloc);
958 char *leaf = basename(leaf_alloc);
959 struct stat statbuf;
960 uid_t p_uid = geteuid();
961
962 if ((dirfd = safe_open(parent_dir, O_RDONLY)) == -1)
963 {
964 goto cleanup;
965 }
966
967 if ((ret = fstatat(dirfd, leaf, &statbuf, AT_SYMLINK_NOFOLLOW)) == -1)
968 {
969 goto cleanup;
970 }
971
972 // Some attacks may use symlinks to get higher privileges
973 // The safe cases are:
974 // * symlinks owned by root
975 // * symlinks owned by the user running the process
976 // * symlinks that have the same owner and group as the destination
977 if (traversed_link &&
978 link_user != 0 &&
979 link_user != p_uid &&
980 (link_user != statbuf.st_uid || link_group != statbuf.st_gid))
981 {
982 errno = ENOLINK;
983 ret = -1;
984 goto cleanup;
985 }
986
987 if (S_ISLNK(statbuf.st_mode) && !(flags & AT_SYMLINK_NOFOLLOW))
988 {
989 if (--loop_countdown <= 0)
990 {
991 ret = -1;
992 errno = ELOOP;
993 goto cleanup;
994 }
995
996 // Add one byte for '\0', and one byte to make sure size doesn't change
997 // in between calls.
998 char *link = xmalloc(statbuf.st_size + 2);
999 ret = readlinkat(dirfd, leaf, link, statbuf.st_size + 1);
1000 if (ret < 0 || ret > statbuf.st_size)
1001 {
1002 // Link either disappeared or was changed under our feet. Be safe
1003 // and bail out.
1004 free(link);
1005 errno = ENOLINK;
1006 ret = -1;
1007 goto cleanup;
1008 }
1009 link[ret] = '\0';
1010
1011 char *resolved_link;
1012 if (link[0] == FILE_SEPARATOR)
1013 {
1014 // Takes ownership of link's memory, so no free().
1015 resolved_link = link;
1016 }
1017 else
1018 {
1019 xasprintf(&resolved_link, "%s%c%s", parent_dir,
1020 FILE_SEPARATOR, link);
1021 free(link);
1022 }
1023
1024 ret = safe_open_true_parent_dir(resolved_link, flags, statbuf.st_uid,
1025 statbuf.st_gid, true, loop_countdown);
1026
1027 free(resolved_link);
1028 goto cleanup;
1029 }
1030
1031 // We now know it either isn't a link, or we don't want to follow it if it
1032 // is. Return the parent dir.
1033 ret = dirfd;
1034 dirfd = -1;
1035
1036 cleanup:
1037 free(parent_dir_alloc);
1038 free(leaf_alloc);
1039
1040 if (dirfd != -1)
1041 {
1042 close(dirfd);
1043 }
1044
1045 return ret;
1046 }
1047
1048 /**
1049 * Implementation of safe_chown.
1050 * @param path Path to chown.
1051 * @param owner Owner to set on path.
1052 * @param group Group to set on path.
1053 * @param flags Flags to use for fchownat.
1054 * @param link_user If we have traversed a link already, which user was it.
1055 * @param link_group If we have traversed a link already, which group was it.
1056 * @param traversed_link Whether we have traversed a link. If this is false the
1057 * two previus arguments are ignored. This is used enforce
1058 * the correct UID/GID combination when following links.
1059 * Initially this is false, but will be set to true in
1060 * sub invocations if we follow a link.
1061 * @param loop_countdown Protection against infinite loop following.
1062 */
safe_chown_impl(const char * path,uid_t owner,gid_t group,int flags)1063 int safe_chown_impl(const char *path, uid_t owner, gid_t group, int flags)
1064 {
1065 int dirfd = safe_open_true_parent_dir(path, flags, 0, 0, false, SYMLINK_MAX_DEPTH);
1066 if (dirfd < 0)
1067 {
1068 return -1;
1069 }
1070
1071 char *leaf_alloc = xstrdup(path);
1072 char *leaf = basename(leaf_alloc);
1073
1074 // We now know it either isn't a link, or we don't want to follow it if it
1075 // is. In either case make sure we don't try to follow it.
1076 flags |= AT_SYMLINK_NOFOLLOW;
1077
1078 int ret = fchownat(dirfd, leaf, owner, group, flags);
1079 free(leaf_alloc);
1080 close(dirfd);
1081 return ret;
1082 }
1083
1084 #endif // !__MINGW32__
1085
1086 /**
1087 * Use this instead of chown(). It changes file owner safely, using safe_open().
1088 * @param path Path to operate on.
1089 * @param owner Owner.
1090 * @param group Group.
1091 * @return Same return values as chown().
1092 */
safe_chown(const char * path,uid_t owner,gid_t group)1093 int safe_chown(const char *path, uid_t owner, gid_t group)
1094 {
1095 #ifdef __MINGW32__
1096 return chown(path, owner, group);
1097 #else // !__MINGW32__
1098 return safe_chown_impl(path, owner, group, 0);
1099 #endif // !__MINGW32__
1100 }
1101
1102 /**
1103 * Use this instead of lchown(). It changes file owner safely, using safe_open().
1104 * @param path Path to operate on.
1105 * @param owner Owner.
1106 * @param group Group.
1107 * @return Same return values as lchown().
1108 */
1109 #ifndef __MINGW32__
safe_lchown(const char * path,uid_t owner,gid_t group)1110 int safe_lchown(const char *path, uid_t owner, gid_t group)
1111 {
1112 return safe_chown_impl(path, owner, group, AT_SYMLINK_NOFOLLOW);
1113 }
1114 #endif // !__MINGW32__
1115
1116 /**
1117 * Use this instead of chmod(). It changes file permissions safely, using safe_open().
1118 * @param path Path to operate on.
1119 * @param mode Permissions.
1120 * @return Same return values as chmod().
1121 */
safe_chmod(const char * path,mode_t mode)1122 int safe_chmod(const char *path, mode_t mode)
1123 {
1124 #ifdef __MINGW32__
1125 return chmod(path, mode);
1126 #else // !__MINGW32__
1127 int dirfd = -1;
1128 int ret = -1;
1129
1130 char *leaf_alloc = xstrdup(path);
1131 char *leaf = basename(leaf_alloc);
1132 struct stat statbuf;
1133 uid_t olduid = 0;
1134
1135 if ((dirfd = safe_open_true_parent_dir(path, 0, 0, 0, false, SYMLINK_MAX_DEPTH)) == -1)
1136 {
1137 goto cleanup;
1138 }
1139
1140 if ((ret = fstatat(dirfd, leaf, &statbuf, AT_SYMLINK_NOFOLLOW)) == -1)
1141 {
1142 goto cleanup;
1143 }
1144
1145 if (S_ISFIFO(statbuf.st_mode) || S_ISSOCK(statbuf.st_mode))
1146 {
1147 /* For FIFOs/sockets we cannot resort to the method of opening the file
1148 first, since it might block. But we also cannot use chmod directly,
1149 because the file may be switched with a symlink to a sensitive file
1150 under our feet, and there is no way to avoid following it. So
1151 instead, switch effective UID to the owner of the FIFO, and then use
1152 chmod.
1153 */
1154
1155 /* save old euid */
1156 olduid = geteuid();
1157
1158 if ((ret = seteuid(statbuf.st_uid)) == -1)
1159 {
1160 goto cleanup;
1161 }
1162
1163 ret = fchmodat(dirfd, leaf, mode, 0);
1164
1165 // Make sure EUID is set back before we check error condition, so that we
1166 // never return with lowered privileges.
1167 if (seteuid(olduid) == -1)
1168 {
1169 ProgrammingError("safe_chmod: Could not set EUID back. Should never happen.");
1170 }
1171
1172 goto cleanup;
1173 }
1174
1175 int file_fd = safe_open(path, 0);
1176 if (file_fd < 0)
1177 {
1178 ret = -1;
1179 goto cleanup;
1180 }
1181
1182 ret = fchmod(file_fd, mode);
1183 close(file_fd);
1184
1185 cleanup:
1186 free(leaf_alloc);
1187
1188 if (dirfd != -1)
1189 {
1190 close(dirfd);
1191 }
1192
1193 return ret;
1194 #endif // !__MINGW32__
1195 }
1196
1197 /**
1198 * Use this instead of creat(). It creates a file safely, using safe_open().
1199 * @param path Path to operate on.
1200 * @param mode Permissions.
1201 * @return Same return values as creat().
1202 */
safe_creat(const char * pathname,mode_t mode)1203 int safe_creat(const char *pathname, mode_t mode)
1204 {
1205 return safe_open_create_perms(pathname,
1206 O_CREAT | O_WRONLY | O_TRUNC,
1207 mode);
1208 }
1209
1210 // Windows implementation in Enterprise.
1211 #ifndef _WIN32
SetCloseOnExec(int fd,bool enable)1212 bool SetCloseOnExec(int fd, bool enable)
1213 {
1214 int flags = fcntl(fd, F_GETFD);
1215 if (enable)
1216 {
1217 flags |= FD_CLOEXEC;
1218 }
1219 else
1220 {
1221 flags &= ~FD_CLOEXEC;
1222 }
1223 return (fcntl(fd, F_SETFD, flags) == 0);
1224 }
1225 #endif // !_WIN32
1226
DeleteDirectoryTreeInternal(const char * basepath,const char * path)1227 static bool DeleteDirectoryTreeInternal(const char *basepath, const char *path)
1228 {
1229 Dir *dirh = DirOpen(path);
1230 const struct dirent *dirp;
1231 bool failed = false;
1232
1233 if (dirh == NULL)
1234 {
1235 if (errno == ENOENT)
1236 {
1237 /* Directory disappeared on its own */
1238 return true;
1239 }
1240
1241 Log(LOG_LEVEL_INFO, "Unable to open directory '%s' during purge of directory tree '%s' (opendir: %s)",
1242 path, basepath, GetErrorStr());
1243 return false;
1244 }
1245
1246 for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
1247 {
1248 if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, ".."))
1249 {
1250 continue;
1251 }
1252
1253 char subpath[PATH_MAX];
1254 snprintf(subpath, sizeof(subpath), "%s" FILE_SEPARATOR_STR "%s", path, dirp->d_name);
1255
1256 struct stat lsb;
1257 if (lstat(subpath, &lsb) == -1)
1258 {
1259 if (errno == ENOENT)
1260 {
1261 /* File disappeared on its own */
1262 continue;
1263 }
1264
1265 Log(LOG_LEVEL_VERBOSE, "Unable to stat file '%s' during purge of directory tree '%s' (lstat: %s)", path, basepath, GetErrorStr());
1266 failed = true;
1267 }
1268 else
1269 {
1270 if (S_ISDIR(lsb.st_mode))
1271 {
1272 if (!DeleteDirectoryTreeInternal(basepath, subpath))
1273 {
1274 failed = true;
1275 }
1276
1277 if (rmdir(subpath) == -1)
1278 {
1279 failed = true;
1280 }
1281 }
1282 else
1283 {
1284 if (unlink(subpath) == -1)
1285 {
1286 if (errno == ENOENT)
1287 {
1288 /* File disappeared on its own */
1289 continue;
1290 }
1291
1292 Log(LOG_LEVEL_VERBOSE, "Unable to remove file '%s' during purge of directory tree '%s'. (unlink: %s)",
1293 subpath, basepath, GetErrorStr());
1294 failed = true;
1295 }
1296 }
1297 }
1298 }
1299
1300 DirClose(dirh);
1301 return !failed;
1302 }
1303
DeleteDirectoryTree(const char * path)1304 bool DeleteDirectoryTree(const char *path)
1305 {
1306 return DeleteDirectoryTreeInternal(path, path);
1307 }
1308
1309 /**
1310 * @NOTE Better use FileSparseCopy() if you are copying file to file
1311 * (that one callse this function).
1312 *
1313 * @NOTE Always use FileSparseWrite() to close the file descriptor, to avoid
1314 * losing data.
1315 */
FileSparseWrite(int fd,const void * buf,size_t count,bool * wrote_hole)1316 bool FileSparseWrite(int fd, const void *buf, size_t count,
1317 bool *wrote_hole)
1318 {
1319 bool all_zeroes = (memcchr(buf, '\0', count) == NULL);
1320
1321 if (all_zeroes) /* write a hole */
1322 {
1323 off_t seek_ret = lseek(fd, count, SEEK_CUR);
1324 if (seek_ret == (off_t) -1)
1325 {
1326 Log(LOG_LEVEL_ERR,
1327 "Failed to write a hole in sparse file (lseek: %s)",
1328 GetErrorStr());
1329 return false;
1330 }
1331 }
1332 else /* write normally */
1333 {
1334 ssize_t w_ret = FullWrite(fd, buf, count);
1335 if (w_ret < 0)
1336 {
1337 Log(LOG_LEVEL_ERR,
1338 "Failed to write to destination file (write: %s)",
1339 GetErrorStr());
1340 return false;
1341 }
1342 }
1343
1344 *wrote_hole = all_zeroes;
1345 return true;
1346 }
1347
1348 /**
1349 * Copy data jumping over areas filled by '\0' greater than blk_size, so
1350 * files automatically become sparse if possible.
1351 *
1352 * File descriptors should already be open, the filenames #source and
1353 * #destination are only for logging purposes.
1354 *
1355 * @NOTE Always use FileSparseClose() to close the file descriptor, to avoid
1356 * losing data.
1357 */
FileSparseCopy(int sd,const char * src_name,int dd,const char * dst_name,size_t blk_size,size_t * total_bytes_written,bool * last_write_was_a_hole)1358 bool FileSparseCopy(int sd, const char *src_name,
1359 int dd, const char *dst_name,
1360 size_t blk_size,
1361 size_t *total_bytes_written,
1362 bool *last_write_was_a_hole)
1363 {
1364 assert(total_bytes_written != NULL);
1365 assert(last_write_was_a_hole != NULL);
1366
1367 const size_t buf_size = blk_size;
1368 void *buf = xmalloc(buf_size);
1369
1370 size_t n_read_total = 0;
1371 bool retval = false;
1372
1373 *last_write_was_a_hole = false;
1374
1375 while (true)
1376 {
1377 ssize_t n_read = FullRead(sd, buf, buf_size);
1378 if (n_read < 0)
1379 {
1380 Log(LOG_LEVEL_ERR,
1381 "Unable to read source file while copying '%s' to '%s'"
1382 " (read: %s)", src_name, dst_name, GetErrorStr());
1383 break;
1384 }
1385 else if (n_read == 0) /* EOF */
1386 {
1387 retval = true;
1388 break;
1389 }
1390
1391 bool ret = FileSparseWrite(dd, buf, n_read,
1392 last_write_was_a_hole);
1393 if (!ret)
1394 {
1395 Log(LOG_LEVEL_ERR, "Failed to copy '%s' to '%s'",
1396 src_name, dst_name);
1397 break;
1398 }
1399
1400 n_read_total += n_read;
1401 }
1402
1403 free(buf);
1404 *total_bytes_written = n_read_total;
1405 return retval;
1406 }
1407
1408 /**
1409 * Always close a written sparse file using this function, else truncation
1410 * might occur if the last part was a hole.
1411 *
1412 * If the tail of the file was a hole (and hence lseek(2)ed on destination
1413 * instead of being written), do a ftruncate(2) here to ensure the whole file
1414 * is written to the disc. But ftruncate() fails with EPERM on non-native
1415 * Linux filesystems (e.g. vfat, vboxfs) when the count is >= than the
1416 * size of the file. So we write() one byte and then ftruncate() it back.
1417 *
1418 * No need for this function to return anything, since the filedescriptor is
1419 * (attempted to) closed in either success or failure.
1420 *
1421 * TODO? instead of needing the #total_bytes_written parameter, we could
1422 * figure the offset after writing the one byte using lseek(fd,0,SEEK_CUR) and
1423 * truncate -1 from that offset. It's probably not worth adding an extra
1424 * system call for simplifying code.
1425 */
FileSparseClose(int fd,const char * filename,bool do_sync,size_t total_bytes_written,bool last_write_was_hole)1426 bool FileSparseClose(int fd, const char *filename,
1427 bool do_sync,
1428 size_t total_bytes_written,
1429 bool last_write_was_hole)
1430 {
1431 if (last_write_was_hole)
1432 {
1433 ssize_t ret1 = FullWrite(fd, "", 1);
1434 if (ret1 == -1)
1435 {
1436 Log(LOG_LEVEL_ERR,
1437 "Failed to close sparse file '%s' (write: %s)",
1438 filename, GetErrorStr());
1439 close(fd);
1440 return false;
1441 }
1442
1443 int ret2 = ftruncate(fd, total_bytes_written);
1444 if (ret2 == -1)
1445 {
1446 Log(LOG_LEVEL_ERR,
1447 "Failed to close sparse file '%s' (ftruncate: %s)",
1448 filename, GetErrorStr());
1449 close(fd);
1450 return false;
1451 }
1452 }
1453
1454 if (do_sync)
1455 {
1456 if (fsync(fd) != 0)
1457 {
1458 Log(LOG_LEVEL_WARNING,
1459 "Could not sync to disk file '%s' (fsync: %s)",
1460 filename, GetErrorStr());
1461 }
1462 }
1463
1464 int ret3 = close(fd);
1465 if (ret3 == -1)
1466 {
1467 Log(LOG_LEVEL_ERR,
1468 "Failed to close file '%s' (close: %s)",
1469 filename, GetErrorStr());
1470 return false;
1471 }
1472
1473 return true;
1474 }
1475
CfReadLine(char ** buff,size_t * size,FILE * fp)1476 ssize_t CfReadLine(char **buff, size_t *size, FILE *fp)
1477 {
1478 ssize_t b = getline(buff, size, fp);
1479 assert(b != 0 && "To the best of my knowledge, getline never returns zero");
1480
1481 if (b > 0)
1482 {
1483 if ((*buff)[b - 1] == '\n')
1484 {
1485 (*buff)[b - 1] = '\0';
1486 b--;
1487 }
1488 }
1489
1490 return b;
1491 }
1492
CfReadLines(char ** buff,size_t * size,FILE * fp,Seq * lines)1493 ssize_t CfReadLines(char **buff, size_t *size, FILE *fp, Seq *lines)
1494 {
1495 assert(size != NULL);
1496 assert(fp != NULL);
1497 assert(lines != NULL);
1498 assert((buff != NULL) || *size == 0);
1499
1500 ssize_t appended = 0;
1501
1502 ssize_t ret;
1503 bool free_buff = (buff == NULL);
1504 while (!feof(fp))
1505 {
1506 assert((buff != NULL) || *size == 0);
1507 ret = CfReadLine(buff, size, fp);
1508 if (ret == -1)
1509 {
1510 if (!feof(fp))
1511 {
1512 if (free_buff)
1513 {
1514 free(*buff);
1515 }
1516 return -1;
1517 }
1518 }
1519 else
1520 {
1521 SeqAppend(lines, xstrdup(*buff));
1522 appended++;
1523 }
1524 }
1525 if (free_buff)
1526 {
1527 free(*buff);
1528 }
1529
1530 return appended;
1531 }
1532
GlobFileList(const char * pattern)1533 StringSet* GlobFileList(const char *pattern)
1534 {
1535 StringSet *set = StringSetNew();
1536 glob_t globbuf;
1537 int globflags = 0; // TODO: maybe add GLOB_BRACE later
1538
1539 const char* r_candidates[] = { "*", "*/*", "*/*/*", "*/*/*/*", "*/*/*/*/*", "*/*/*/*/*/*" };
1540 bool starstar = ( strstr(pattern, "**") != NULL );
1541 const char** candidates = starstar ? r_candidates : NULL;
1542 const int candidate_count = starstar ? 6 : 1;
1543
1544 for (int pi = 0; pi < candidate_count; pi++)
1545 {
1546 char *expanded = starstar ?
1547 SearchAndReplace(pattern, "**", candidates[pi]) :
1548 xstrdup(pattern);
1549
1550 #ifdef _WIN32
1551 if (strchr(expanded, '\\'))
1552 {
1553 Log(LOG_LEVEL_VERBOSE, "Found backslash escape character in glob pattern '%s'. "
1554 "Was forward slash intended?", expanded);
1555 }
1556 #endif
1557
1558 if (glob(expanded, globflags, NULL, &globbuf) == 0)
1559 {
1560 for (size_t i = 0; i < globbuf.gl_pathc; i++)
1561 {
1562 StringSetAdd(set, xstrdup(globbuf.gl_pathv[i]));
1563 }
1564
1565 globfree(&globbuf);
1566 }
1567
1568 free(expanded);
1569 }
1570
1571 return set;
1572 }
1573
1574 /*******************************************************************/
1575
GetRelocatedProcdirRoot()1576 const char* GetRelocatedProcdirRoot()
1577 {
1578 const char *procdir = getenv("CFENGINE_TEST_OVERRIDE_PROCDIR");
1579 if (procdir == NULL)
1580 {
1581 procdir = "";
1582 }
1583 else
1584 {
1585 Log(LOG_LEVEL_VERBOSE, "Overriding /proc location to be %s", procdir);
1586 }
1587
1588 return procdir;
1589 }
1590
1591
1592 #if !defined(__MINGW32__)
1593
LockFD(int fd,short int lock_type,bool wait)1594 static int LockFD(int fd, short int lock_type, bool wait)
1595 {
1596 struct flock lock_spec = {
1597 .l_type = lock_type,
1598 .l_whence = SEEK_SET,
1599 .l_start = 0, /* start of the region to which the lock applies */
1600 .l_len = 0 /* till EOF */
1601 };
1602
1603 if (wait)
1604 {
1605 while (fcntl(fd, F_SETLKW, &lock_spec) == -1)
1606 {
1607 if (errno != EINTR)
1608 {
1609 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1610 fd, GetErrorStr());
1611 return -1;
1612 }
1613 }
1614 return 0;
1615 }
1616 else
1617 {
1618 if (fcntl(fd, F_SETLK, &lock_spec) == -1)
1619 {
1620 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1621 fd, GetErrorStr());
1622 return -1;
1623 }
1624 /* else */
1625 return 0;
1626 }
1627 }
1628
UnlockFD(int fd)1629 static int UnlockFD(int fd)
1630 {
1631 struct flock lock_spec = {
1632 .l_type = F_UNLCK,
1633 .l_whence = SEEK_SET,
1634 .l_start = 0, /* start of the region to which the lock applies */
1635 .l_len = 0 /* till EOF */
1636 };
1637
1638 if (fcntl(fd, F_SETLK, &lock_spec) == -1)
1639 {
1640 Log(LOG_LEVEL_DEBUG, "Failed to release file lock for FD %d: %s",
1641 fd, GetErrorStr());
1642 return -1;
1643 }
1644 /* else */
1645 return 0;
1646 }
1647
ExclusiveFileLock(FileLock * lock,bool wait)1648 int ExclusiveFileLock(FileLock *lock, bool wait)
1649 {
1650 assert(lock != NULL);
1651 assert(lock->fd >= 0);
1652
1653 return LockFD(lock->fd, F_WRLCK, wait);
1654 }
1655
SharedFileLock(FileLock * lock,bool wait)1656 int SharedFileLock(FileLock *lock, bool wait)
1657 {
1658 assert(lock != NULL);
1659 assert(lock->fd >= 0);
1660
1661 return LockFD(lock->fd, F_RDLCK, wait);
1662 }
1663
ExclusiveFileLockCheck(FileLock * lock)1664 bool ExclusiveFileLockCheck(FileLock *lock)
1665 {
1666 assert(lock != NULL);
1667 assert(lock->fd >= 0);
1668
1669 struct flock lock_spec = {
1670 .l_type = F_WRLCK,
1671 .l_whence = SEEK_SET,
1672 .l_start = 0, /* start of the region to which the lock applies */
1673 .l_len = 0 /* till EOF */
1674 };
1675 if (fcntl(lock->fd, F_GETLK, &lock_spec) == -1)
1676 {
1677 /* should never happen */
1678 Log(LOG_LEVEL_ERR, "Error when checking locks on FD %d", lock->fd);
1679 return false;
1680 }
1681 return (lock_spec.l_type == F_UNLCK);
1682 }
1683
ExclusiveFileUnlock(FileLock * lock,bool close_fd)1684 int ExclusiveFileUnlock(FileLock *lock, bool close_fd)
1685 {
1686 assert(lock != NULL);
1687 assert(lock->fd >= 0);
1688
1689 if (close_fd)
1690 {
1691 /* also releases the lock */
1692 int ret = close(lock->fd);
1693 if (ret != 0)
1694 {
1695 Log(LOG_LEVEL_ERR, "Failed to close lock file with FD %d: %s",
1696 lock->fd, GetErrorStr());
1697 lock->fd = -1;
1698 return -1;
1699 }
1700 /* else*/
1701 lock->fd = -1;
1702 return 0;
1703 }
1704 else
1705 {
1706 return UnlockFD(lock->fd);
1707 }
1708 }
1709
SharedFileUnlock(FileLock * lock,bool close_fd)1710 int SharedFileUnlock(FileLock *lock, bool close_fd)
1711 {
1712 assert(lock != NULL);
1713 assert(lock->fd >= 0);
1714
1715 /* unlocking is the same for both kinds of locks */
1716 return ExclusiveFileUnlock(lock, close_fd);
1717 }
1718
1719 #else /* __MINGW32__ */
1720
LockFD(int fd,DWORD flags,bool wait)1721 static int LockFD(int fd, DWORD flags, bool wait)
1722 {
1723 OVERLAPPED ol = { 0 };
1724 ol.Offset = INT_MAX;
1725
1726 if (!wait)
1727 {
1728 flags |= LOCKFILE_FAIL_IMMEDIATELY;
1729 }
1730
1731 HANDLE fh = (HANDLE)_get_osfhandle(fd);
1732
1733 if (!LockFileEx(fh, flags, 0, 1, 0, &ol))
1734 {
1735 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1736 fd, GetErrorStr());
1737 return -1;
1738 }
1739
1740 return 0;
1741 }
1742
ExclusiveFileLock(FileLock * lock,bool wait)1743 int ExclusiveFileLock(FileLock *lock, bool wait)
1744 {
1745 assert(lock != NULL);
1746 assert(lock->fd >= 0);
1747
1748 return LockFD(lock->fd, LOCKFILE_EXCLUSIVE_LOCK, wait);
1749 }
1750
SharedFileLock(FileLock * lock,bool wait)1751 int SharedFileLock(FileLock *lock, bool wait)
1752 {
1753 assert(lock != NULL);
1754 assert(lock->fd >= 0);
1755
1756 return LockFD(lock->fd, 0, wait);
1757 }
1758
UnlockFD(int fd)1759 static int UnlockFD(int fd)
1760 {
1761 OVERLAPPED ol = { 0 };
1762 ol.Offset = INT_MAX;
1763
1764 HANDLE fh = (HANDLE)_get_osfhandle(fd);
1765
1766 if (!UnlockFileEx(fh, 0, 1, 0, &ol))
1767 {
1768 Log(LOG_LEVEL_DEBUG, "Failed to release file lock for FD %d: %s",
1769 fd, GetErrorStr());
1770 return -1;
1771 }
1772
1773 return 0;
1774 }
1775
ExclusiveFileLockCheck(FileLock * lock)1776 bool ExclusiveFileLockCheck(FileLock *lock)
1777 {
1778 /* XXX: there seems to be no way to check if the current process is holding
1779 * a lock on a file */
1780 return false;
1781 }
1782
ExclusiveFileUnlock(FileLock * lock,bool close_fd)1783 int ExclusiveFileUnlock(FileLock *lock, bool close_fd)
1784 {
1785 assert(lock != NULL);
1786 assert(lock->fd >= 0);
1787
1788 int ret = UnlockFD(lock->fd);
1789 if (close_fd)
1790 {
1791 close(lock->fd);
1792 lock->fd = -1;
1793 }
1794 return ret;
1795 }
1796
SharedFileUnlock(FileLock * lock,bool close_fd)1797 int SharedFileUnlock(FileLock *lock, bool close_fd)
1798 {
1799 assert(lock != NULL);
1800 assert(lock->fd >= 0);
1801
1802 /* unlocking is the same for both kinds of locks */
1803 return ExclusiveFileUnlock(lock, close_fd);
1804 }
1805
1806 #endif /* __MINGW32__ */
1807
ExclusiveFileLockPath(FileLock * lock,const char * fpath,bool wait)1808 int ExclusiveFileLockPath(FileLock *lock, const char *fpath, bool wait)
1809 {
1810 assert(lock != NULL);
1811 assert(lock->fd < 0);
1812
1813 int fd = safe_open(fpath, O_CREAT|O_RDWR);
1814 if (fd < 0)
1815 {
1816 Log(LOG_LEVEL_ERR, "Failed to open '%s' for locking", fpath);
1817 return -2;
1818 }
1819
1820 lock->fd = fd;
1821 int ret = ExclusiveFileLock(lock, wait);
1822 if (ret != 0)
1823 {
1824 lock->fd = -1;
1825 }
1826 return ret;
1827 }
1828
SharedFileLockPath(FileLock * lock,const char * fpath,bool wait)1829 int SharedFileLockPath(FileLock *lock, const char *fpath, bool wait)
1830 {
1831 assert(lock != NULL);
1832 assert(lock->fd < 0);
1833
1834 int fd = safe_open(fpath, O_CREAT|O_RDONLY);
1835 if (fd < 0)
1836 {
1837 Log(LOG_LEVEL_ERR, "Failed to open '%s' for locking", fpath);
1838 return -2;
1839 }
1840
1841 lock->fd = fd;
1842 int ret = SharedFileLock(lock, wait);
1843 if (ret != 0)
1844 {
1845 lock->fd = -1;
1846 }
1847 return ret;
1848 }
1849