From a35bfef24c97123223bb1f8cd383fee8d6be8e76 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Thu, 31 Mar 2022 16:31:13 +0900 Subject: [PATCH] [sys] Add a function to safely create a unique file QF currently uses unique file names for screenshots and server-side demos (and remote snapshots), but they're generally useful. QFS_NextFilename has been filling this role, but it is highly insecure in its current implementation. This is the first step to taking care of that. --- include/QF/sys.h | 33 +++++++++++++++++++++++++++++++++ libs/util/sys.c | 27 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/include/QF/sys.h b/include/QF/sys.h index 631489d79..3b6d5aed3 100644 --- a/include/QF/sys.h +++ b/include/QF/sys.h @@ -38,6 +38,8 @@ #include #include +struct dstring_s; + extern struct cvar_s *sys_nostdout; extern struct cvar_s *sys_extrasleep; extern struct cvar_s *sys_dead_sleep; @@ -166,6 +168,37 @@ int Sys_CreatePath (const char *path); */ char *Sys_ExpandSquiggle (const char *path); +/** Open a newly created file with a guaranteed unique name. + + Uniqueness is guaranteed by adding a numeric sequence between the \a + prefix and \a suffix, with a minium of \a mindigits numeric characters + (with any required leading 0s to expand the number to \a mindigits). + + The created file has read and write permissions as modified by the OS, + and the handle can be bothe written and read. + + \param name dstring into which the name will be generated. Any + existing contents will be lost. If an error occurs, + \a name will be set to the error string. + \param prefix This includes the path to the file and any file name + prefix. The numeric sequence will be appended directly + to the prefix with no directory separator. + \param suffix Optional tail to be appended after the numeric sequence, + usually the file extension. A dot is not added + automatically, it is up to the caller to supply one. NULL + and an empty string are equivalent. + \param mindigits The minimum number of digits to include in the + generated file name. The sequence number will be padded + with 0s in order to meet this menimum. Overflow will + simply produce longer numeric sequence sub-strings. + \return File handle to the newly created file, or a negative + value if an error occured (the negative error code). + Suitable for use with read, write, fdopen, Qdopen, etc. + \note It is the caller's responsibility to close the file. +*/ +int Sys_UniqueFile (struct dstring_s *name, const char *prefix, + const char *suffix, int mindigits); + ///@} #endif//__QF_sys_h diff --git a/libs/util/sys.c b/libs/util/sys.c index 8aefe89f5..daa9adb55 100644 --- a/libs/util/sys.c +++ b/libs/util/sys.c @@ -60,6 +60,7 @@ # include #endif +#include #include #include #include @@ -1155,3 +1156,29 @@ Sys_ExpandSquiggle (const char *path) home = "."; return nva ("%s%s", home, path + 1); // skip leading ~ } + +VISIBLE int +Sys_UniqueFile (dstring_t *name, const char *prefix, const char *suffix, + int mindigits) +{ + const int flags = O_CREAT | O_EXCL | O_RDWR; + const int mode = 0644; + int64_t seq = 0; // it should take a while to run out + + if (!suffix) { + suffix = ""; + } + while (1) { + dsprintf (name, "%s%0*"PRIi64"%s", prefix, mindigits, seq, suffix); + int fd = open (name->str, flags, mode); + if (fd >= 0) { + return fd; + } + int err = errno; + if (err != EEXIST) { + dsprintf (name, "%s", strerror (err)); + return -err; + } + seq++; + } +}