From: Baptiste Daroussin Date: Sun, 28 Jun 2026 05:51:37 +0000 (+0200) Subject: Add mkuniqfileat, factor unique-file creation X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b85ab82ddac143bd83f9f8638c42751ecabdf301;p=thirdparty%2Fmlmmj.git Add mkuniqfileat, factor unique-file creation 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. --- diff --git a/include/strgen.h b/include/strgen.h index 3fac0820..a25ededc 100644 --- a/include/strgen.h +++ b/include/strgen.h @@ -25,9 +25,22 @@ #define STRGEN_H #include +#include char *random_str(void); char *random_plus_addr(const char *addr); +/* + * Atomically create a uniquely-named file under dirfd. Its name is + * "" (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); diff --git a/src/mlmmj-receive.c b/src/mlmmj-receive.c index ac48fc27..63db5fda 100644 --- a/src/mlmmj-receive.c +++ b/src/mlmmj-receive.c @@ -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); diff --git a/src/prepstdreply.c b/src/prepstdreply.c index dc33ee08..40a4b389 100644 --- a/src/prepstdreply.c +++ b/src/prepstdreply.c @@ -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; diff --git a/src/send_digest.c b/src/send_digest.c index b0fe0c1c..43d5bf63 100644 --- a/src/send_digest.c +++ b/src/send_digest.c @@ -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; } diff --git a/src/strgen.c b/src/strgen.c index cd894ba1..e64a4ed5 100644 --- a/src/strgen.c +++ b/src/strgen.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -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; diff --git a/src/subscriberfuncs.c b/src/subscriberfuncs.c index 27a4847a..8de734c0 100644 --- a/src/subscriberfuncs.c +++ b/src/subscriberfuncs.c @@ -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); } diff --git a/tests/mlmmj.c b/tests/mlmmj.c index e595dd97..c4e5b7db 100644 --- a/tests/mlmmj.c +++ b/tests/mlmmj.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -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);