1 /*
2 Copyright 2020 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 #else // !__MINGW32__
566
567 const size_t path_bufsize = strlen(pathname) + 1;
568 char path[path_bufsize];
569 const size_t res_len = StringCopy(pathname, path, path_bufsize);
570 UNUSED(res_len);
571 assert(res_len == strlen(pathname));
572 int currentfd;
573 const char *first_dir;
574 bool trunc = false;
575 const int orig_flags = flags;
576 char *next_component = path;
577 bool p_uid;
578
579 if (*next_component == '/')
580 {
581 first_dir = "/";
582 // Eliminate double slashes.
583 while (*(++next_component) == '/') { /*noop*/ }
584 if (!*next_component)
585 {
586 next_component = NULL;
587 }
588 }
589 else
590 {
591 first_dir = ".";
592 }
593 currentfd = openat(AT_FDCWD, first_dir, O_RDONLY);
594 if (currentfd < 0)
595 {
596 return -1;
597 }
598
599 // current process user id
600 p_uid = geteuid();
601
602 size_t final_size = (size_t) -1;
603 while (next_component)
604 {
605 char *component = next_component;
606 next_component = strchr(component + 1, '/');
607 // Used to restore the slashes in the final path component.
608 char *restore_slash = NULL;
609 if (next_component)
610 {
611 restore_slash = next_component;
612 *next_component = '\0';
613 // Eliminate double slashes.
614 while (*(++next_component) == '/') { /*noop*/ }
615 if (!*next_component)
616 {
617 next_component = NULL;
618 }
619 else
620 {
621 restore_slash = NULL;
622 }
623 }
624
625 // In cases of a race condition when creating a file, our attempt to open it may fail
626 // (see O_EXCL and O_CREAT flags below). However, this can happen even under normal
627 // circumstances, if we are unlucky; it does not mean that someone is up to something bad.
628 // So retry it a few times before giving up.
629 int attempts = 3;
630 trunc = false;
631 while (true)
632 {
633
634 if (( (orig_flags & O_RDWR) || (orig_flags & O_WRONLY))
635 && (orig_flags & O_TRUNC))
636 {
637 trunc = true;
638 /* We need to check after we have opened the file whether we
639 * opened the right one. But if we truncate it, the damage is
640 * already done, we have destroyed the contents, and that file
641 * might have been a symlink to /etc/shadow! So save that flag
642 * and apply the truncation afterwards instead. */
643 flags &= ~O_TRUNC;
644 }
645
646 if (restore_slash)
647 {
648 *restore_slash = '\0';
649 }
650
651 struct stat stat_before, stat_after;
652 int stat_before_result = fstatat(currentfd, component, &stat_before, AT_SYMLINK_NOFOLLOW);
653 if (stat_before_result < 0
654 && (errno != ENOENT
655 || next_component // Meaning "not a leaf".
656 || !(flags & O_CREAT)))
657 {
658 close(currentfd);
659 return -1;
660 }
661
662 /*
663 * This testing entry point is about the following real-world
664 * scenario: There can be an attacker that at this point
665 * overwrites the existing file or writes a file, invalidating
666 * basically the previous fstatat().
667 *
668 * - We make sure that can't happen if the file did not exist, by
669 * creating with O_EXCL.
670 * - We make sure that can't happen if the file existed, by
671 * comparing with fstat() result after the open().
672 *
673 */
674 TEST_SYMLINK_SWITCH_POINT
675
676 if (!next_component) /* last component */
677 {
678 if (stat_before_result < 0)
679 {
680 assert(flags & O_CREAT);
681
682 // Doesn't exist. Make sure *we* create it.
683 flags |= O_EXCL;
684
685 /* No need to ftruncate() the file at the end. */
686 trunc = false;
687 }
688 else
689 {
690 if ((flags & O_CREAT) && (flags & O_EXCL))
691 {
692 close(currentfd);
693 errno = EEXIST;
694 return -1;
695 }
696
697 // Already exists. Make sure we *don't* create it.
698 flags &= ~O_CREAT;
699 }
700 if (restore_slash)
701 {
702 *restore_slash = '/';
703 }
704 int filefd = openat(currentfd, component, flags, create_perms);
705 if (filefd < 0)
706 {
707 if ((stat_before_result < 0 && !(orig_flags & O_EXCL) && errno == EEXIST) ||
708 (stat_before_result >= 0 && (orig_flags & O_CREAT) && errno == ENOENT))
709 {
710 if (--attempts >= 0)
711 {
712 // Might be our fault. Try again.
713 flags = orig_flags;
714 continue;
715 }
716 else
717 {
718 // Too many attempts. Give up.
719 // Most likely a link that is in the way of file creation, but can also
720 // be a file that is constantly created and deleted (race condition).
721 // It is not possible to separate between the two, so return EACCES to
722 // signal that we denied access.
723 errno = EACCES;
724 }
725 }
726 close(currentfd);
727 return -1;
728 }
729 close(currentfd);
730 currentfd = filefd;
731 }
732 else
733 {
734 int new_currentfd = openat(currentfd, component, O_RDONLY);
735 close(currentfd);
736 if (new_currentfd < 0)
737 {
738 return -1;
739 }
740 currentfd = new_currentfd;
741 }
742
743 /* If file did exist, we fstat() again and compare with previous. */
744
745 if (stat_before_result == 0)
746 {
747 if (fstat(currentfd, &stat_after) < 0)
748 {
749 close(currentfd);
750 return -1;
751 }
752 // Some attacks may use symlinks to get higher privileges
753 // The safe cases are:
754 // * symlinks owned by root
755 // * symlinks owned by the user running the process
756 // * symlinks that have the same owner and group as the destination
757 if (stat_before.st_uid != 0 &&
758 stat_before.st_uid != p_uid &&
759 (stat_before.st_uid != stat_after.st_uid || stat_before.st_gid != stat_after.st_gid))
760 {
761 close(currentfd);
762 Log(LOG_LEVEL_ERR, "Cannot follow symlink '%s'; it is not "
763 "owned by root or the user running this process, and "
764 "the target owner and/or group differs from that of "
765 "the symlink itself.", pathname);
766 // Return ENOLINK to signal that the link cannot be followed
767 // ('Link has been severed').
768 errno = ENOLINK;
769 return -1;
770 }
771
772 final_size = (size_t) stat_after.st_size;
773 }
774
775 // If we got here, we've been successful, so don't try again.
776 break;
777 }
778 }
779
780 /* Truncate if O_CREAT and the file preexisted. */
781 if (trunc)
782 {
783 /* Do not truncate if the size is already zero, some
784 * filesystems don't support ftruncate() with offset>=size. */
785 assert(final_size != (size_t) -1);
786
787 if (final_size != 0)
788 {
789 int tr_ret = ftruncate(currentfd, 0);
790 if (tr_ret < 0)
791 {
792 Log(LOG_LEVEL_ERR,
793 "safe_open: unexpected failure (ftruncate: %s)",
794 GetErrorStr());
795 close(currentfd);
796 return -1;
797 }
798 }
799 }
800
801 return currentfd;
802 #endif // !__MINGW32__
803 }
804
safe_fopen(const char * const path,const char * const mode)805 FILE *safe_fopen(const char *const path, const char *const mode)
806 {
807 return safe_fopen_create_perms(path, mode, CF_PERMS_DEFAULT);
808 }
809
810 /**
811 * Opens a file safely. It will follow symlinks, but only if the symlink is trusted,
812 * that is, if the owner of the symlink and the owner of the target are the same,
813 * or if the owner of the symlink is either root or the user running the current process.
814 * All components are checked, even symlinks encountered in earlier parts of the
815 * path name.
816 *
817 * It should always be used when opening a directory that is not guaranteed to be
818 * owned by root.
819 *
820 * @param pathname The path to open.
821 * @param flags Same mode as for system fopen().
822 * @param create_perms Permissions for file, only relevant on file creation.
823 * @return Same errors as fopen().
824 */
safe_fopen_create_perms(const char * const path,const char * const mode,const mode_t create_perms)825 FILE *safe_fopen_create_perms(
826 const char *const path, const char *const mode, const mode_t create_perms)
827 {
828 if (!path || !mode)
829 {
830 errno = EINVAL;
831 return NULL;
832 }
833
834 int flags = 0;
835 for (int c = 0; mode[c]; c++)
836 {
837 switch (mode[c])
838 {
839 case 'r':
840 flags |= O_RDONLY;
841 break;
842 case 'w':
843 flags |= O_WRONLY | O_TRUNC | O_CREAT;
844 break;
845 case 'a':
846 flags |= O_WRONLY | O_CREAT;
847 break;
848 case '+':
849 flags &= ~(O_RDONLY | O_WRONLY);
850 flags |= O_RDWR;
851 break;
852 case 'b':
853 flags |= O_BINARY;
854 break;
855 case 't':
856 flags |= O_TEXT;
857 break;
858 case 'x':
859 flags |= O_EXCL;
860 break;
861 default:
862 ProgrammingError("Invalid flag for fopen: %s", mode);
863 return NULL;
864 }
865 }
866
867 int fd = safe_open_create_perms(path, flags, create_perms);
868 if (fd < 0)
869 {
870 return NULL;
871 }
872 FILE *ret = fdopen(fd, mode);
873 if (!ret)
874 {
875 close(fd);
876 return NULL;
877 }
878
879 if (mode[0] == 'a')
880 {
881 if (fseek(ret, 0, SEEK_END) < 0)
882 {
883 fclose(ret);
884 return NULL;
885 }
886 }
887
888 return ret;
889 }
890
891 /**
892 * Use this instead of chdir(). It changes into the directory safely, using safe_open().
893 * @param path Path to change into.
894 * @return Same return values as chdir().
895 */
safe_chdir(const char * path)896 int safe_chdir(const char *path)
897 {
898 #ifdef __MINGW32__
899 return chdir(path);
900 #else // !__MINGW32__
901 int fd = safe_open(path, O_RDONLY);
902 if (fd < 0)
903 {
904 return -1;
905 }
906 if (fchdir(fd) < 0)
907 {
908 close(fd);
909 return -1;
910 }
911 close(fd);
912 return 0;
913 #endif // !__MINGW32__
914 }
915
916 #ifndef __MINGW32__
917
918 /**
919 * Opens the true parent dir of the file in the path given. The notable
920 * difference from doing it the naive way (open(dirname(path))) is that it
921 * can follow the symlinks of the path, ending up in the true parent dir of the
922 * path. It follows the same safe mechanisms as `safe_open()` to do so. If
923 * AT_SYMLINK_NOFOLLOW is given, it is equivalent to doing it the naive way (but
924 * still following "safe" semantics).
925 * @param path Path to open parent directory of.
926 * @param flags Flags to use for fchownat.
927 * @param link_user If we have traversed a link already, which user was it.
928 * @param link_group If we have traversed a link already, which group was it.
929 * @param traversed_link Whether we have traversed a link. If this is false the
930 * two previus arguments are ignored. This is used enforce
931 * the correct UID/GID combination when following links.
932 * Initially this is false, but will be set to true in
933 * sub invocations if we follow a link.
934 * @param loop_countdown Protection against infinite loop following.
935 * @return File descriptor pointing to the parent directory of path, or -1 on
936 * error.
937 */
safe_open_true_parent_dir(const char * path,int flags,uid_t link_user,gid_t link_group,bool traversed_link,int loop_countdown)938 static int safe_open_true_parent_dir(const char *path,
939 int flags,
940 uid_t link_user,
941 gid_t link_group,
942 bool traversed_link,
943 int loop_countdown)
944 {
945 int dirfd = -1;
946 int ret = -1;
947
948 char *parent_dir_alloc = xstrdup(path);
949 char *leaf_alloc = xstrdup(path);
950 char *parent_dir = dirname(parent_dir_alloc);
951 char *leaf = basename(leaf_alloc);
952 struct stat statbuf;
953 uid_t p_uid = geteuid();
954
955 if ((dirfd = safe_open(parent_dir, O_RDONLY)) == -1)
956 {
957 goto cleanup;
958 }
959
960 if ((ret = fstatat(dirfd, leaf, &statbuf, AT_SYMLINK_NOFOLLOW)) == -1)
961 {
962 goto cleanup;
963 }
964
965 // Some attacks may use symlinks to get higher privileges
966 // The safe cases are:
967 // * symlinks owned by root
968 // * symlinks owned by the user running the process
969 // * symlinks that have the same owner and group as the destination
970 if (traversed_link &&
971 link_user != 0 &&
972 link_user != p_uid &&
973 (link_user != statbuf.st_uid || link_group != statbuf.st_gid))
974 {
975 errno = ENOLINK;
976 ret = -1;
977 goto cleanup;
978 }
979
980 if (S_ISLNK(statbuf.st_mode) && !(flags & AT_SYMLINK_NOFOLLOW))
981 {
982 if (--loop_countdown <= 0)
983 {
984 ret = -1;
985 errno = ELOOP;
986 goto cleanup;
987 }
988
989 // Add one byte for '\0', and one byte to make sure size doesn't change
990 // in between calls.
991 char *link = xmalloc(statbuf.st_size + 2);
992 ret = readlinkat(dirfd, leaf, link, statbuf.st_size + 1);
993 if (ret < 0 || ret > statbuf.st_size)
994 {
995 // Link either disappeared or was changed under our feet. Be safe
996 // and bail out.
997 free(link);
998 errno = ENOLINK;
999 ret = -1;
1000 goto cleanup;
1001 }
1002 link[ret] = '\0';
1003
1004 char *resolved_link;
1005 if (link[0] == FILE_SEPARATOR)
1006 {
1007 // Takes ownership of link's memory, so no free().
1008 resolved_link = link;
1009 }
1010 else
1011 {
1012 xasprintf(&resolved_link, "%s%c%s", parent_dir,
1013 FILE_SEPARATOR, link);
1014 free(link);
1015 }
1016
1017 ret = safe_open_true_parent_dir(resolved_link, flags, statbuf.st_uid,
1018 statbuf.st_gid, true, loop_countdown);
1019
1020 free(resolved_link);
1021 goto cleanup;
1022 }
1023
1024 // We now know it either isn't a link, or we don't want to follow it if it
1025 // is. Return the parent dir.
1026 ret = dirfd;
1027 dirfd = -1;
1028
1029 cleanup:
1030 free(parent_dir_alloc);
1031 free(leaf_alloc);
1032
1033 if (dirfd != -1)
1034 {
1035 close(dirfd);
1036 }
1037
1038 return ret;
1039 }
1040
1041 /**
1042 * Implementation of safe_chown.
1043 * @param path Path to chown.
1044 * @param owner Owner to set on path.
1045 * @param group Group to set on path.
1046 * @param flags Flags to use for fchownat.
1047 * @param link_user If we have traversed a link already, which user was it.
1048 * @param link_group If we have traversed a link already, which group was it.
1049 * @param traversed_link Whether we have traversed a link. If this is false the
1050 * two previus arguments are ignored. This is used enforce
1051 * the correct UID/GID combination when following links.
1052 * Initially this is false, but will be set to true in
1053 * sub invocations if we follow a link.
1054 * @param loop_countdown Protection against infinite loop following.
1055 */
safe_chown_impl(const char * path,uid_t owner,gid_t group,int flags)1056 int safe_chown_impl(const char *path, uid_t owner, gid_t group, int flags)
1057 {
1058 int dirfd = safe_open_true_parent_dir(path, flags, 0, 0, false, SYMLINK_MAX_DEPTH);
1059 if (dirfd < 0)
1060 {
1061 return -1;
1062 }
1063
1064 char *leaf_alloc = xstrdup(path);
1065 char *leaf = basename(leaf_alloc);
1066
1067 // We now know it either isn't a link, or we don't want to follow it if it
1068 // is. In either case make sure we don't try to follow it.
1069 flags |= AT_SYMLINK_NOFOLLOW;
1070
1071 int ret = fchownat(dirfd, leaf, owner, group, flags);
1072 free(leaf_alloc);
1073 close(dirfd);
1074 return ret;
1075 }
1076
1077 #endif // !__MINGW32__
1078
1079 /**
1080 * Use this instead of chown(). It changes file owner safely, using safe_open().
1081 * @param path Path to operate on.
1082 * @param owner Owner.
1083 * @param group Group.
1084 * @return Same return values as chown().
1085 */
safe_chown(const char * path,uid_t owner,gid_t group)1086 int safe_chown(const char *path, uid_t owner, gid_t group)
1087 {
1088 #ifdef __MINGW32__
1089 return chown(path, owner, group);
1090 #else // !__MINGW32__
1091 return safe_chown_impl(path, owner, group, 0);
1092 #endif // !__MINGW32__
1093 }
1094
1095 /**
1096 * Use this instead of lchown(). It changes file owner safely, using safe_open().
1097 * @param path Path to operate on.
1098 * @param owner Owner.
1099 * @param group Group.
1100 * @return Same return values as lchown().
1101 */
1102 #ifndef __MINGW32__
safe_lchown(const char * path,uid_t owner,gid_t group)1103 int safe_lchown(const char *path, uid_t owner, gid_t group)
1104 {
1105 return safe_chown_impl(path, owner, group, AT_SYMLINK_NOFOLLOW);
1106 }
1107 #endif // !__MINGW32__
1108
1109 /**
1110 * Use this instead of chmod(). It changes file permissions safely, using safe_open().
1111 * @param path Path to operate on.
1112 * @param mode Permissions.
1113 * @return Same return values as chmod().
1114 */
safe_chmod(const char * path,mode_t mode)1115 int safe_chmod(const char *path, mode_t mode)
1116 {
1117 #ifdef __MINGW32__
1118 return chmod(path, mode);
1119 #else // !__MINGW32__
1120 int dirfd = -1;
1121 int ret = -1;
1122
1123 char *leaf_alloc = xstrdup(path);
1124 char *leaf = basename(leaf_alloc);
1125 struct stat statbuf;
1126 uid_t olduid = 0;
1127
1128 if ((dirfd = safe_open_true_parent_dir(path, 0, 0, 0, false, SYMLINK_MAX_DEPTH)) == -1)
1129 {
1130 goto cleanup;
1131 }
1132
1133 if ((ret = fstatat(dirfd, leaf, &statbuf, AT_SYMLINK_NOFOLLOW)) == -1)
1134 {
1135 goto cleanup;
1136 }
1137
1138 if (S_ISFIFO(statbuf.st_mode) || S_ISSOCK(statbuf.st_mode))
1139 {
1140 /* For FIFOs/sockets we cannot resort to the method of opening the file
1141 first, since it might block. But we also cannot use chmod directly,
1142 because the file may be switched with a symlink to a sensitive file
1143 under our feet, and there is no way to avoid following it. So
1144 instead, switch effective UID to the owner of the FIFO, and then use
1145 chmod.
1146 */
1147
1148 /* save old euid */
1149 olduid = geteuid();
1150
1151 if ((ret = seteuid(statbuf.st_uid)) == -1)
1152 {
1153 goto cleanup;
1154 }
1155
1156 ret = fchmodat(dirfd, leaf, mode, 0);
1157
1158 // Make sure EUID is set back before we check error condition, so that we
1159 // never return with lowered privileges.
1160 if (seteuid(olduid) == -1)
1161 {
1162 ProgrammingError("safe_chmod: Could not set EUID back. Should never happen.");
1163 }
1164
1165 goto cleanup;
1166 }
1167
1168 int file_fd = safe_open(path, 0);
1169 if (file_fd < 0)
1170 {
1171 ret = -1;
1172 goto cleanup;
1173 }
1174
1175 ret = fchmod(file_fd, mode);
1176 close(file_fd);
1177
1178 cleanup:
1179 free(leaf_alloc);
1180
1181 if (dirfd != -1)
1182 {
1183 close(dirfd);
1184 }
1185
1186 return ret;
1187 #endif // !__MINGW32__
1188 }
1189
1190 /**
1191 * Use this instead of creat(). It creates a file safely, using safe_open().
1192 * @param path Path to operate on.
1193 * @param mode Permissions.
1194 * @return Same return values as creat().
1195 */
safe_creat(const char * pathname,mode_t mode)1196 int safe_creat(const char *pathname, mode_t mode)
1197 {
1198 return safe_open_create_perms(pathname,
1199 O_CREAT | O_WRONLY | O_TRUNC,
1200 mode);
1201 }
1202
1203 // Windows implementation in Enterprise.
1204 #ifndef _WIN32
SetCloseOnExec(int fd,bool enable)1205 bool SetCloseOnExec(int fd, bool enable)
1206 {
1207 int flags = fcntl(fd, F_GETFD);
1208 if (enable)
1209 {
1210 flags |= FD_CLOEXEC;
1211 }
1212 else
1213 {
1214 flags &= ~FD_CLOEXEC;
1215 }
1216 return (fcntl(fd, F_SETFD, flags) == 0);
1217 }
1218 #endif // !_WIN32
1219
DeleteDirectoryTreeInternal(const char * basepath,const char * path)1220 static bool DeleteDirectoryTreeInternal(const char *basepath, const char *path)
1221 {
1222 Dir *dirh = DirOpen(path);
1223 const struct dirent *dirp;
1224 bool failed = false;
1225
1226 if (dirh == NULL)
1227 {
1228 if (errno == ENOENT)
1229 {
1230 /* Directory disappeared on its own */
1231 return true;
1232 }
1233
1234 Log(LOG_LEVEL_INFO, "Unable to open directory '%s' during purge of directory tree '%s' (opendir: %s)",
1235 path, basepath, GetErrorStr());
1236 return false;
1237 }
1238
1239 for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
1240 {
1241 if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, ".."))
1242 {
1243 continue;
1244 }
1245
1246 char subpath[PATH_MAX];
1247 snprintf(subpath, sizeof(subpath), "%s" FILE_SEPARATOR_STR "%s", path, dirp->d_name);
1248
1249 struct stat lsb;
1250 if (lstat(subpath, &lsb) == -1)
1251 {
1252 if (errno == ENOENT)
1253 {
1254 /* File disappeared on its own */
1255 continue;
1256 }
1257
1258 Log(LOG_LEVEL_VERBOSE, "Unable to stat file '%s' during purge of directory tree '%s' (lstat: %s)", path, basepath, GetErrorStr());
1259 failed = true;
1260 }
1261 else
1262 {
1263 if (S_ISDIR(lsb.st_mode))
1264 {
1265 if (!DeleteDirectoryTreeInternal(basepath, subpath))
1266 {
1267 failed = true;
1268 }
1269
1270 if (rmdir(subpath) == -1)
1271 {
1272 failed = true;
1273 }
1274 }
1275 else
1276 {
1277 if (unlink(subpath) == -1)
1278 {
1279 if (errno == ENOENT)
1280 {
1281 /* File disappeared on its own */
1282 continue;
1283 }
1284
1285 Log(LOG_LEVEL_VERBOSE, "Unable to remove file '%s' during purge of directory tree '%s'. (unlink: %s)",
1286 subpath, basepath, GetErrorStr());
1287 failed = true;
1288 }
1289 }
1290 }
1291 }
1292
1293 DirClose(dirh);
1294 return !failed;
1295 }
1296
DeleteDirectoryTree(const char * path)1297 bool DeleteDirectoryTree(const char *path)
1298 {
1299 return DeleteDirectoryTreeInternal(path, path);
1300 }
1301
1302 /**
1303 * @NOTE Better use FileSparseCopy() if you are copying file to file
1304 * (that one callse this function).
1305 *
1306 * @NOTE Always use FileSparseWrite() to close the file descriptor, to avoid
1307 * losing data.
1308 */
FileSparseWrite(int fd,const void * buf,size_t count,bool * wrote_hole)1309 bool FileSparseWrite(int fd, const void *buf, size_t count,
1310 bool *wrote_hole)
1311 {
1312 bool all_zeroes = (memcchr(buf, '\0', count) == NULL);
1313
1314 if (all_zeroes) /* write a hole */
1315 {
1316 off_t seek_ret = lseek(fd, count, SEEK_CUR);
1317 if (seek_ret == (off_t) -1)
1318 {
1319 Log(LOG_LEVEL_ERR,
1320 "Failed to write a hole in sparse file (lseek: %s)",
1321 GetErrorStr());
1322 return false;
1323 }
1324 }
1325 else /* write normally */
1326 {
1327 ssize_t w_ret = FullWrite(fd, buf, count);
1328 if (w_ret < 0)
1329 {
1330 Log(LOG_LEVEL_ERR,
1331 "Failed to write to destination file (write: %s)",
1332 GetErrorStr());
1333 return false;
1334 }
1335 }
1336
1337 *wrote_hole = all_zeroes;
1338 return true;
1339 }
1340
1341 /**
1342 * Copy data jumping over areas filled by '\0' greater than blk_size, so
1343 * files automatically become sparse if possible.
1344 *
1345 * File descriptors should already be open, the filenames #source and
1346 * #destination are only for logging purposes.
1347 *
1348 * @NOTE Always use FileSparseClose() to close the file descriptor, to avoid
1349 * losing data.
1350 */
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)1351 bool FileSparseCopy(int sd, const char *src_name,
1352 int dd, const char *dst_name,
1353 size_t blk_size,
1354 size_t *total_bytes_written,
1355 bool *last_write_was_a_hole)
1356 {
1357 assert(total_bytes_written != NULL);
1358 assert(last_write_was_a_hole != NULL);
1359
1360 const size_t buf_size = blk_size;
1361 void *buf = xmalloc(buf_size);
1362
1363 size_t n_read_total = 0;
1364 bool retval = false;
1365
1366 *last_write_was_a_hole = false;
1367
1368 while (true)
1369 {
1370 ssize_t n_read = FullRead(sd, buf, buf_size);
1371 if (n_read < 0)
1372 {
1373 Log(LOG_LEVEL_ERR,
1374 "Unable to read source file while copying '%s' to '%s'"
1375 " (read: %s)", src_name, dst_name, GetErrorStr());
1376 break;
1377 }
1378 else if (n_read == 0) /* EOF */
1379 {
1380 retval = true;
1381 break;
1382 }
1383
1384 bool ret = FileSparseWrite(dd, buf, n_read,
1385 last_write_was_a_hole);
1386 if (!ret)
1387 {
1388 Log(LOG_LEVEL_ERR, "Failed to copy '%s' to '%s'",
1389 src_name, dst_name);
1390 break;
1391 }
1392
1393 n_read_total += n_read;
1394 }
1395
1396 free(buf);
1397 *total_bytes_written = n_read_total;
1398 return retval;
1399 }
1400
1401 /**
1402 * Always close a written sparse file using this function, else truncation
1403 * might occur if the last part was a hole.
1404 *
1405 * If the tail of the file was a hole (and hence lseek(2)ed on destination
1406 * instead of being written), do a ftruncate(2) here to ensure the whole file
1407 * is written to the disc. But ftruncate() fails with EPERM on non-native
1408 * Linux filesystems (e.g. vfat, vboxfs) when the count is >= than the
1409 * size of the file. So we write() one byte and then ftruncate() it back.
1410 *
1411 * No need for this function to return anything, since the filedescriptor is
1412 * (attempted to) closed in either success or failure.
1413 *
1414 * TODO? instead of needing the #total_bytes_written parameter, we could
1415 * figure the offset after writing the one byte using lseek(fd,0,SEEK_CUR) and
1416 * truncate -1 from that offset. It's probably not worth adding an extra
1417 * system call for simplifying code.
1418 */
FileSparseClose(int fd,const char * filename,bool do_sync,size_t total_bytes_written,bool last_write_was_hole)1419 bool FileSparseClose(int fd, const char *filename,
1420 bool do_sync,
1421 size_t total_bytes_written,
1422 bool last_write_was_hole)
1423 {
1424 if (last_write_was_hole)
1425 {
1426 ssize_t ret1 = FullWrite(fd, "", 1);
1427 if (ret1 == -1)
1428 {
1429 Log(LOG_LEVEL_ERR,
1430 "Failed to close sparse file '%s' (write: %s)",
1431 filename, GetErrorStr());
1432 close(fd);
1433 return false;
1434 }
1435
1436 int ret2 = ftruncate(fd, total_bytes_written);
1437 if (ret2 == -1)
1438 {
1439 Log(LOG_LEVEL_ERR,
1440 "Failed to close sparse file '%s' (ftruncate: %s)",
1441 filename, GetErrorStr());
1442 close(fd);
1443 return false;
1444 }
1445 }
1446
1447 if (do_sync)
1448 {
1449 if (fsync(fd) != 0)
1450 {
1451 Log(LOG_LEVEL_WARNING,
1452 "Could not sync to disk file '%s' (fsync: %s)",
1453 filename, GetErrorStr());
1454 }
1455 }
1456
1457 int ret3 = close(fd);
1458 if (ret3 == -1)
1459 {
1460 Log(LOG_LEVEL_ERR,
1461 "Failed to close file '%s' (close: %s)",
1462 filename, GetErrorStr());
1463 return false;
1464 }
1465
1466 return true;
1467 }
1468
CfReadLine(char ** buff,size_t * size,FILE * fp)1469 ssize_t CfReadLine(char **buff, size_t *size, FILE *fp)
1470 {
1471 ssize_t b = getline(buff, size, fp);
1472 assert(b != 0 && "To the best of my knowledge, getline never returns zero");
1473
1474 if (b > 0)
1475 {
1476 if ((*buff)[b - 1] == '\n')
1477 {
1478 (*buff)[b - 1] = '\0';
1479 b--;
1480 }
1481 }
1482
1483 return b;
1484 }
1485
GlobFileList(const char * pattern)1486 StringSet* GlobFileList(const char *pattern)
1487 {
1488 StringSet *set = StringSetNew();
1489 glob_t globbuf;
1490 int globflags = 0; // TODO: maybe add GLOB_BRACE later
1491
1492 const char* r_candidates[] = { "*", "*/*", "*/*/*", "*/*/*/*", "*/*/*/*/*", "*/*/*/*/*/*" };
1493 bool starstar = ( strstr(pattern, "**") != NULL );
1494 const char** candidates = starstar ? r_candidates : NULL;
1495 const int candidate_count = starstar ? 6 : 1;
1496
1497 for (int pi = 0; pi < candidate_count; pi++)
1498 {
1499 char *expanded = starstar ?
1500 SearchAndReplace(pattern, "**", candidates[pi]) :
1501 xstrdup(pattern);
1502
1503 #ifdef _WIN32
1504 if (strchr(expanded, '\\'))
1505 {
1506 Log(LOG_LEVEL_VERBOSE, "Found backslash escape character in glob pattern '%s'. "
1507 "Was forward slash intended?", expanded);
1508 }
1509 #endif
1510
1511 if (glob(expanded, globflags, NULL, &globbuf) == 0)
1512 {
1513 for (int i = 0; i < globbuf.gl_pathc; i++)
1514 {
1515 StringSetAdd(set, xstrdup(globbuf.gl_pathv[i]));
1516 }
1517
1518 globfree(&globbuf);
1519 }
1520
1521 free(expanded);
1522 }
1523
1524 return set;
1525 }
1526
1527 /*******************************************************************/
1528
GetRelocatedProcdirRoot()1529 const char* GetRelocatedProcdirRoot()
1530 {
1531 const char *procdir = getenv("CFENGINE_TEST_OVERRIDE_PROCDIR");
1532 if (procdir == NULL)
1533 {
1534 procdir = "";
1535 }
1536 else
1537 {
1538 Log(LOG_LEVEL_VERBOSE, "Overriding /proc location to be %s", procdir);
1539 }
1540
1541 return procdir;
1542 }
1543
1544
1545 #if !defined(__MINGW32__)
1546
LockFD(int fd,short int lock_type,bool wait)1547 static int LockFD(int fd, short int lock_type, bool wait)
1548 {
1549 struct flock lock_spec = {
1550 .l_type = lock_type,
1551 .l_whence = SEEK_SET,
1552 .l_start = 0, /* start of the region to which the lock applies */
1553 .l_len = 0 /* till EOF */
1554 };
1555
1556 if (wait)
1557 {
1558 while (fcntl(fd, F_SETLKW, &lock_spec) == -1)
1559 {
1560 if (errno != EINTR)
1561 {
1562 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1563 fd, GetErrorStr());
1564 return -1;
1565 }
1566 }
1567 return 0;
1568 }
1569 else
1570 {
1571 if (fcntl(fd, F_SETLK, &lock_spec) == -1)
1572 {
1573 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1574 fd, GetErrorStr());
1575 return -1;
1576 }
1577 /* else */
1578 return 0;
1579 }
1580 }
1581
UnlockFD(int fd)1582 static int UnlockFD(int fd)
1583 {
1584 struct flock lock_spec = {
1585 .l_type = F_UNLCK,
1586 .l_whence = SEEK_SET,
1587 .l_start = 0, /* start of the region to which the lock applies */
1588 .l_len = 0 /* till EOF */
1589 };
1590
1591 if (fcntl(fd, F_SETLK, &lock_spec) == -1)
1592 {
1593 Log(LOG_LEVEL_DEBUG, "Failed to release file lock for FD %d: %s",
1594 fd, GetErrorStr());
1595 return -1;
1596 }
1597 /* else */
1598 return 0;
1599 }
1600
ExclusiveFileLock(FileLock * lock,bool wait)1601 int ExclusiveFileLock(FileLock *lock, bool wait)
1602 {
1603 assert(lock != NULL);
1604 assert(lock->fd >= 0);
1605
1606 return LockFD(lock->fd, F_WRLCK, wait);
1607 }
1608
SharedFileLock(FileLock * lock,bool wait)1609 int SharedFileLock(FileLock *lock, bool wait)
1610 {
1611 assert(lock != NULL);
1612 assert(lock->fd >= 0);
1613
1614 return LockFD(lock->fd, F_RDLCK, wait);
1615 }
1616
ExclusiveFileLockCheck(FileLock * lock)1617 bool ExclusiveFileLockCheck(FileLock *lock)
1618 {
1619 assert(lock != NULL);
1620 assert(lock->fd >= 0);
1621
1622 struct flock lock_spec = {
1623 .l_type = F_WRLCK,
1624 .l_whence = SEEK_SET,
1625 .l_start = 0, /* start of the region to which the lock applies */
1626 .l_len = 0 /* till EOF */
1627 };
1628 if (fcntl(lock->fd, F_GETLK, &lock_spec) == -1)
1629 {
1630 /* should never happen */
1631 Log(LOG_LEVEL_ERR, "Error when checking locks on FD %d", lock->fd);
1632 return false;
1633 }
1634 return (lock_spec.l_type == F_UNLCK);
1635 }
1636
ExclusiveFileUnlock(FileLock * lock,bool close_fd)1637 int ExclusiveFileUnlock(FileLock *lock, bool close_fd)
1638 {
1639 assert(lock != NULL);
1640 assert(lock->fd >= 0);
1641
1642 if (close_fd)
1643 {
1644 /* also releases the lock */
1645 int ret = close(lock->fd);
1646 if (ret != 0)
1647 {
1648 Log(LOG_LEVEL_ERR, "Failed to close lock file with FD %d: %s",
1649 lock->fd, GetErrorStr());
1650 lock->fd = -1;
1651 return -1;
1652 }
1653 /* else*/
1654 lock->fd = -1;
1655 return 0;
1656 }
1657 else
1658 {
1659 return UnlockFD(lock->fd);
1660 }
1661 }
1662
SharedFileUnlock(FileLock * lock,bool close_fd)1663 int SharedFileUnlock(FileLock *lock, bool close_fd)
1664 {
1665 assert(lock != NULL);
1666 assert(lock->fd >= 0);
1667
1668 /* unlocking is the same for both kinds of locks */
1669 return ExclusiveFileUnlock(lock, close_fd);
1670 }
1671
1672 #else /* __MINGW32__ */
1673
LockFD(int fd,DWORD flags,bool wait)1674 static int LockFD(int fd, DWORD flags, bool wait)
1675 {
1676 OVERLAPPED ol = { 0 };
1677 ol.Offset = INT_MAX;
1678
1679 if (!wait)
1680 {
1681 flags |= LOCKFILE_FAIL_IMMEDIATELY;
1682 }
1683
1684 HANDLE fh = (HANDLE)_get_osfhandle(fd);
1685
1686 if (!LockFileEx(fh, flags, 0, 1, 0, &ol))
1687 {
1688 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1689 fd, GetErrorStr());
1690 return -1;
1691 }
1692
1693 return 0;
1694 }
1695
ExclusiveFileLock(FileLock * lock,bool wait)1696 int ExclusiveFileLock(FileLock *lock, bool wait)
1697 {
1698 assert(lock != NULL);
1699 assert(lock->fd >= 0);
1700
1701 return LockFD(lock->fd, LOCKFILE_EXCLUSIVE_LOCK, wait);
1702 }
1703
SharedFileLock(FileLock * lock,bool wait)1704 int SharedFileLock(FileLock *lock, bool wait)
1705 {
1706 assert(lock != NULL);
1707 assert(lock->fd >= 0);
1708
1709 return LockFD(lock->fd, 0, wait);
1710 }
1711
UnlockFD(int fd)1712 static int UnlockFD(int fd)
1713 {
1714 OVERLAPPED ol = { 0 };
1715 ol.Offset = INT_MAX;
1716
1717 HANDLE fh = (HANDLE)_get_osfhandle(fd);
1718
1719 if (!UnlockFileEx(fh, 0, 1, 0, &ol))
1720 {
1721 Log(LOG_LEVEL_DEBUG, "Failed to release file lock for FD %d: %s",
1722 fd, GetErrorStr());
1723 return -1;
1724 }
1725
1726 return 0;
1727 }
1728
ExclusiveFileLockCheck(FileLock * lock)1729 bool ExclusiveFileLockCheck(FileLock *lock)
1730 {
1731 /* XXX: there seems to be no way to check if the current process is holding
1732 * a lock on a file */
1733 return false;
1734 }
1735
ExclusiveFileUnlock(FileLock * lock,bool close_fd)1736 int ExclusiveFileUnlock(FileLock *lock, bool close_fd)
1737 {
1738 assert(lock != NULL);
1739 assert(lock->fd >= 0);
1740
1741 int ret = UnlockFD(lock->fd);
1742 if (close_fd)
1743 {
1744 close(lock->fd);
1745 lock->fd = -1;
1746 }
1747 return ret;
1748 }
1749
SharedFileUnlock(FileLock * lock,bool close_fd)1750 int SharedFileUnlock(FileLock *lock, bool close_fd)
1751 {
1752 assert(lock != NULL);
1753 assert(lock->fd >= 0);
1754
1755 /* unlocking is the same for both kinds of locks */
1756 return ExclusiveFileUnlock(lock, close_fd);
1757 }
1758
1759 #endif /* __MINGW32__ */
1760
ExclusiveFileLockPath(FileLock * lock,const char * fpath,bool wait)1761 int ExclusiveFileLockPath(FileLock *lock, const char *fpath, bool wait)
1762 {
1763 assert(lock != NULL);
1764 assert(lock->fd < 0);
1765
1766 int fd = safe_open(fpath, O_CREAT|O_RDWR);
1767 if (fd < 0)
1768 {
1769 Log(LOG_LEVEL_ERR, "Failed to open '%s' for locking", fpath);
1770 return -2;
1771 }
1772
1773 lock->fd = fd;
1774 int ret = ExclusiveFileLock(lock, wait);
1775 if (ret != 0)
1776 {
1777 lock->fd = -1;
1778 }
1779 return ret;
1780 }
1781
SharedFileLockPath(FileLock * lock,const char * fpath,bool wait)1782 int SharedFileLockPath(FileLock *lock, const char *fpath, bool wait)
1783 {
1784 assert(lock != NULL);
1785 assert(lock->fd < 0);
1786
1787 int fd = safe_open(fpath, O_CREAT|O_RDONLY);
1788 if (fd < 0)
1789 {
1790 Log(LOG_LEVEL_ERR, "Failed to open '%s' for locking", fpath);
1791 return -2;
1792 }
1793
1794 lock->fd = fd;
1795 int ret = SharedFileLock(lock, wait);
1796 if (ret != 0)
1797 {
1798 lock->fd = -1;
1799 }
1800 return ret;
1801 }
1802