]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
Add mkuniqfileat, factor unique-file creation
authorBaptiste Daroussin <bapt@FreeBSD.org>
Sun, 28 Jun 2026 05:51:37 +0000 (07:51 +0200)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Sun, 28 Jun 2026 05:53:33 +0000 (07:53 +0200)
Centralize the random_str + O_CREAT|O_EXCL + EEXIST retry recipe into
mkuniqfileat() and use it in mlmmj-receive, subscriberfuncs (subconf),
send_digest and prepstdreply.  Two sites reusing the random suffix as
a cookie are left unchanged.  Add a unit test.

include/strgen.h
src/mlmmj-receive.c
src/prepstdreply.c
src/send_digest.c
src/strgen.c
src/subscriberfuncs.c
tests/mlmmj.c

index 3fac0820dbd676076459d3ccfc40be9b9ad9a00f..a25ededc2fbd671910ac79402f63fa43b178abc3 100644 (file)
 #define STRGEN_H
 
 #include <stdbool.h>
+#include <sys/types.h>
 
 char *random_str(void);
 char *random_plus_addr(const char *addr);
+/*
+ * Atomically create a uniquely-named file under dirfd.  Its name is
+ * "<prefix><random>" (prefix may be NULL or empty); the file is opened
+ * O_RDWR|O_CREAT|O_EXCL and the call retries on EEXIST.  On success
+ * returns the open fd and, if name_out != NULL, a malloc'd copy of the
+ * basename that was created (caller frees).  On other errors returns -1
+ * with errno set and sets *name_out to NULL.  Use AT_FDCWD with an
+ * absolute or list-relative prefix to create via an absolute path.
+ */
+int mkuniqfileat(int dirfd, const char *prefix, mode_t mode,
+    char **name_out);
+char *random_plus_addr(const char *addr);
 char *genlistname(const char *listaddr);
 const char *genlistfqdn(const char *listaddr);
 char *hostnamestr(void);
index ac48fc2753f9925f6d73a8dddafcb6e46a61e88b..63db5fda22404df7f71aeca03b70f586b3845ee6 100644 (file)
@@ -373,22 +373,17 @@ int main(int argc, char **argv)
        if (incfd == -1)
                err(EXIT_FAILURE, "Cannot open(%s/incoming)", listdir);
 
-       do {
-               free(randomstr);
-               randomstr = random_str();
-               fd = openat(incfd, randomstr, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
-       } while(fd < 0 && errno == EEXIST);
-
-       xasprintf(&infilename, "%s/incoming/%s", listdir, randomstr);
-       free(randomstr);
-
-       if(fd < 0) {
+       fd = mkuniqfileat(incfd, "", S_IRUSR|S_IWUSR, &randomstr);
+       if (fd < 0) {
                log_error(LOG_ARGS, "could not create mail file in "
                                    "%s/incoming directory", listdir);
-               free(infilename);
+               free(randomstr);
                exit(EXIT_FAILURE);
        }
 
+       xasprintf(&infilename, "%s/incoming/%s", listdir, randomstr);
+       free(randomstr);
+
        if(process_mail(STDIN_FILENO, fd, listfd) != 0) {
                log_error(LOG_ARGS, "Could not receive mail");
                exit(EXIT_FAILURE);
index dc33ee08c1b2804968eaf5c8b82c139454d6d661..40a4b3892aab17a1750b0e8151861c78b2e697ab 100644 (file)
@@ -1758,22 +1758,15 @@ char *prepstdreply(text *txt, struct ml *ml, const char *from, const char *to, c
 {
        int outfd;
        FILE *outf;
-       char *tmp, *retstr = NULL;
+       char *qprefix, *retstr = NULL;
 
-
-       do {
-               tmp = random_str();
-               if (retstr)
-                       free(retstr);
-               xasprintf(&retstr, "%s/queue/%s", ml->dir, tmp);
-               free(tmp);
-
-               outfd = open(retstr, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
-
-       } while ((outfd < 0) && (errno == EEXIST));
+       xasprintf(&qprefix, "%s/queue/", ml->dir);
+       outfd = mkuniqfileat(AT_FDCWD, qprefix, S_IRUSR|S_IWUSR, &retstr);
+       free(qprefix);
 
        if(outfd < 0) {
-               log_error(LOG_ARGS, "Could not open std mail %s", retstr);
+               log_error(LOG_ARGS, "Could not open std mail in %s/queue",
+                   ml->dir);
                free(retstr);
                close_text(txt);
                return NULL;
index b0fe0c1c0ba6a95f84c1df1a4cb7ada49ade09fb..43d5bf6333798ecd4760bd9d27223051f294ef23 100644 (file)
@@ -225,7 +225,7 @@ int send_digest(struct ml *ml, int firstindex, int lastindex,
        size_t len;
        text *txt = NULL;
        char buf[100];
-       char *tmp, *queuename = NULL, *subject = NULL, *line = NULL;
+       char *tmp, *queuename = NULL, *subject = NULL, *line = NULL, *qprefix;
        char *boundary = NULL;
        thread_list_state *tls;
        FILE *f = NULL;
@@ -240,17 +240,12 @@ int send_digest(struct ml *ml, int firstindex, int lastindex,
        if (firstindex > lastindex)
                return -1;
 
-       do {
-               tmp = random_str();
-               free(queuename);
-               xasprintf(&queuename, "%s/queue/%s", ml->dir, tmp);
-               free(tmp);
-               fd = open(queuename, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
-       } while ((fd < 0) && (errno == EEXIST));
-
+       xasprintf(&qprefix, "%s/queue/", ml->dir);
+       fd = mkuniqfileat(AT_FDCWD, qprefix, S_IRUSR|S_IWUSR, &queuename);
+       free(qprefix);
        if (fd < 0) {
-               log_error(LOG_ARGS, "Could not open digest queue file '%s'",
-                               queuename);
+               log_error(LOG_ARGS, "Could not open digest queue file in "
+                               "%s/queue", ml->dir);
                free(queuename);
                return -1;
        }
index cd894ba15acab5256cfe7e20062bb716545f6aec..e64a4ed534b603612dc02b9f4ac7482bc680026d 100644 (file)
@@ -26,6 +26,8 @@
 #include <string.h>
 #include <stdarg.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 #include <netdb.h>
 #include <libgen.h>
 #include <time.h>
@@ -52,6 +54,37 @@ char *random_str(void)
        return dest;
 }
 
+int
+mkuniqfileat(int dirfd, const char *prefix, mode_t mode, char **name_out)
+{
+       char *name = NULL;
+       int fd;
+
+       if (prefix == NULL)
+               prefix = "";
+       for (;;) {
+               char *r = random_str();
+
+               xasprintf(&name, "%s%s", prefix, r);
+               free(r);
+               fd = openat(dirfd, name, O_RDWR|O_CREAT|O_EXCL, mode);
+               if (fd >= 0)
+                       break;
+               free(name);
+               name = NULL;
+               if (errno != EEXIST) {
+                       if (name_out != NULL)
+                               *name_out = NULL;
+                       return (-1);
+               }
+       }
+       if (name_out != NULL)
+               *name_out = name;
+       else
+               free(name);
+       return (fd);
+}
+
 char *genlistname(const char *listaddr)
 {
        const char *atsign;
index 27a4847a3735e5f934e05a5cad8e034803d4e147..8de734c01ea3f0abacd005a10ab02bf8a0569df3 100644 (file)
@@ -305,15 +305,10 @@ generate_subconfirm(struct ml *ml, const char *subaddr, enum subtype typesub,
                exit(EXIT_FAILURE);
        }
 
-       do {
-               free(randomstr);
-               randomstr = random_str();
-               subconffd = openat(fd, randomstr, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
-       } while ((subconffd < 0) && (errno == EEXIST));
-
-       if(subconffd < 0) {
-               log_error(LOG_ARGS, "Could not open '%s/%s/%s'", ml->dir,
-                   sub ? "subconf" : "unsubconf", randomstr);
+       subconffd = mkuniqfileat(fd, "", S_IRUSR|S_IWUSR, &randomstr);
+       if (subconffd < 0) {
+               log_error(LOG_ARGS, "Could not open '%s/%s' unique file",
+                   ml->dir, sub ? "subconf" : "unsubconf");
                free(randomstr);
                exit(EXIT_FAILURE);
        }
index e595dd97899c8dcb354f79c5fc4bde89e08b9794..c4e5b7db0ca85b911a054cfbf5842dd151123718 100644 (file)
@@ -41,6 +41,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <limits.h>
+#include <unistd.h>
 #include <dirent.h>
 #include <errno.h>
 #include <signal.h>
@@ -136,6 +137,7 @@ ATF_TC_WITHOUT_HEAD(unsubscribe_test);
 ATF_TC_WITHOUT_HEAD(find_addr_line_edge);
 ATF_TC_WITHOUT_HEAD(is_subbed_crlf_prefix);
 ATF_TC_WITHOUT_HEAD(unsubscribe_crlf_prefix);
+ATF_TC_WITHOUT_HEAD(mkuniqfileat_test);
 ATF_TC_WITHOUT_HEAD(getaddrsfromfile);
 ATF_TC_WITHOUT_HEAD(dumpfd2fd);
 ATF_TC_WITHOUT_HEAD(dumpfd2fd_large);
@@ -1717,6 +1719,66 @@ ATF_TC_BODY(unsubscribe_crlf_prefix, tc)
        }
 }
 
+ATF_TC_BODY(mkuniqfileat_test, tc)
+{
+       int dfd, fd, fd2;
+       char *name = NULL, *name2 = NULL, *name3 = NULL, *name4 = NULL;
+
+       ATF_REQUIRE(mkdir("mkuniq", 0755) == 0);
+       dfd = open("mkuniq", O_DIRECTORY);
+       ATF_REQUIRE(dfd >= 0);
+
+       /* Bare unique name, returned via name_out. */
+       fd = mkuniqfileat(dfd, "", S_IRUSR|S_IWUSR, &name);
+       ATF_REQUIRE_MSG(fd >= 0, "mkuniqfileat failed: %s", strerror(errno));
+       ATF_REQUIRE(name != NULL);
+       ATF_REQUIRE_MSG(faccessat(dfd, name, F_OK, 0) == 0,
+           "created file not visible");
+       ATF_REQUIRE(dprintf(fd, "x") == 1);
+       close(fd);
+
+       /* A second call must yield a different name. */
+       fd2 = mkuniqfileat(dfd, "", S_IRUSR|S_IWUSR, &name2);
+       ATF_REQUIRE(fd2 >= 0);
+       ATF_REQUIRE(name2 != NULL);
+       ATF_REQUIRE_MSG(strcmp(name, name2) != 0,
+           "two calls produced the same name %s", name);
+       close(fd2);
+
+       /* Prefixed name. */
+       fd = mkuniqfileat(dfd, "pre", S_IRUSR|S_IWUSR, &name3);
+       ATF_REQUIRE(fd >= 0);
+       ATF_REQUIRE_MSG(strncmp(name3, "pre", 3) == 0,
+           "prefix not honoured: %s", name3);
+       ATF_REQUIRE_MSG(faccessat(dfd, name3, F_OK, 0) == 0,
+           "prefixed file not visible");
+       close(fd);
+
+       /* name_out == NULL is allowed. */
+       fd = mkuniqfileat(dfd, "", S_IRUSR|S_IWUSR, NULL);
+       ATF_REQUIRE(fd >= 0);
+       close(fd);
+
+       /* AT_FDCWD with a relative path prefix (absolute-path usage). */
+       fd = mkuniqfileat(AT_FDCWD, "mkuniq/", S_IRUSR|S_IWUSR, &name4);
+       ATF_REQUIRE(fd >= 0);
+       ATF_REQUIRE_MSG(strncmp(name4, "mkuniq/", 7) == 0,
+           "cwd-prefix not honoured: %s", name4);
+       ATF_REQUIRE_MSG(access(name4, F_OK) == 0, "cwd-prefix file not visible");
+       close(fd);
+
+       /* Bad dirfd: returns -1, does not exit, sets *name_out to NULL. */
+       fd = mkuniqfileat(-1, "", S_IRUSR|S_IWUSR, &name);
+       ATF_REQUIRE_EQ_MSG(fd, -1, "expected failure on bad dirfd");
+       ATF_REQUIRE(name == NULL);
+
+       free(name);
+       free(name2);
+       free(name3);
+       free(name4);
+       close(dfd);
+}
+
 ATF_TC_BODY(getaddrsfromfile, tc)
 {
        strlist stl = vec_init();
@@ -5491,6 +5553,7 @@ ATF_TP_ADD_TCS(tp)
        ATF_TP_ADD_TC(tp, find_addr_line_edge);
        ATF_TP_ADD_TC(tp, is_subbed_crlf_prefix);
        ATF_TP_ADD_TC(tp, unsubscribe_crlf_prefix);
+       ATF_TP_ADD_TC(tp, mkuniqfileat_test);
        ATF_TP_ADD_TC(tp, getaddrsfromfile);
        ATF_TP_ADD_TC(tp, dumpfd2fd);
        ATF_TP_ADD_TC(tp, dumpfd2fd_large);