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