1
0
mirror of https://github.com/janet-lang/janet synced 2026-03-03 06:19:48 +00:00

Dup io file descriptors when marshalling closable files.

For unclosable files, no need to dup, but for closable files we can get
a resource leak. Valgrind and similar tools won't catch this but IO will
unexpectedly start going to the wrong descriptor if a file was
transferred to a new thread, closed, and then a new file was created.
This commit is contained in:
Calvin Rose
2026-03-01 10:27:32 -06:00
parent 5fec2aa9df
commit 32d75c9e49
4 changed files with 45 additions and 10 deletions

View File

@@ -4014,7 +4014,7 @@
"handler not supported for :datagram servers")
(def s (net/listen host port type no-reuse))
(if handler
(ev/go (fn [] (net/accept-loop s handler))))
(ev/go (fn :net/server-handler [] (net/accept-loop s handler))))
s))
###
@@ -4670,8 +4670,7 @@
(defn- run-main
[env subargs arg]
(when-let [entry (in env 'main)
main (or (get entry :value) (in (get entry :ref) 0))]
(when-let [main (module/value env 'main)]
(def guard (if (get env :debug) :ydt :y))
(defn wrap-main [&]
(main ;subargs))

View File

@@ -109,10 +109,11 @@ static int32_t checkflags(const uint8_t *str) {
return flags;
}
static void *makef(FILE *f, int32_t flags) {
static void *makef(FILE *f, int32_t flags, size_t bufsize) {
JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile));
iof->file = f;
iof->flags = flags;
iof->vbufsize = bufsize;
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
/* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is
* not standard. */
@@ -164,6 +165,7 @@ JANET_CORE_FN(cfun_io_fopen,
flags = JANET_FILE_READ;
}
FILE *f = fopen((const char *)fname, (const char *)fmode);
size_t bufsize = BUFSIZ;
if (f != NULL) {
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
struct stat st;
@@ -173,7 +175,7 @@ JANET_CORE_FN(cfun_io_fopen,
janet_panicf("cannot open directory: %s", fname);
}
#endif
size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
if (bufsize != BUFSIZ) {
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);
if (result) {
@@ -181,7 +183,7 @@ JANET_CORE_FN(cfun_io_fopen,
}
}
}
return f ? janet_makefile(f, flags)
return f ? janet_wrap_abstract(makef(f, flags, bufsize))
: (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil())
: janet_wrap_nil();
}
@@ -410,12 +412,23 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) {
JanetFile *iof = (JanetFile *)p;
if (ctx->flags & JANET_MARSHAL_UNSAFE) {
janet_marshal_abstract(ctx, p);
int fno = -1;
#ifdef JANET_WINDOWS
janet_marshal_int(ctx, _fileno(iof->file));
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
fno = _fileno(iof->file);
} else {
fno = _dup(_fileno(iof->file));
}
#else
janet_marshal_int(ctx, fileno(iof->file));
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
fno = fileno(iof->file);
} else {
fno = dup(fileno(iof->file));
}
#endif
janet_marshal_int(ctx, fno);
janet_marshal_int(ctx, iof->flags);
janet_marshal_size(ctx, iof->vbufsize);
} else {
janet_panic("cannot marshal file in safe mode");
}
@@ -444,6 +457,11 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx) {
} else {
iof->flags = flags;
}
iof->vbufsize = janet_unmarshal_size(ctx);
if (iof->vbufsize != BUFSIZ) {
int result = setvbuf(iof->file, NULL, iof->vbufsize ? _IOFBF : _IONBF, iof->vbufsize);
janet_assert(!result, "unmarshal setvbuf");
}
return iof;
} else {
janet_panic("cannot unmarshal file in safe mode");
@@ -785,11 +803,11 @@ FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) {
}
JanetFile *janet_makejfile(FILE *f, int32_t flags) {
return makef(f, flags);
return makef(f, flags, BUFSIZ);
}
Janet janet_makefile(FILE *f, int32_t flags) {
return janet_wrap_abstract(makef(f, flags));
return janet_wrap_abstract(makef(f, flags, BUFSIZ));
}
JanetAbstract janet_checkfile(Janet j) {

View File

@@ -1281,6 +1281,7 @@ typedef struct JanetFile JanetFile;
struct JanetFile {
FILE *file;
int32_t flags;
size_t vbufsize;
};
/* For janet_try and janet_restore */

View File

@@ -84,4 +84,21 @@
(assert-error "cannot schedule non-new fiber"
(ev/go f))
# IO file copying
(os/mkdir "tmp")
(def f-original (file/open "tmp/out.txt" :wb))
(xprin f-original "hello\n")
(file/flush f-original)
(ev/do-thread
# Closes a COPY of the original file, otherwise we get a user-after-close file descriptor
(:close f-original))
(def g-original (file/open "tmp/out2.txt" :wb))
(xprin g-original "world1\n")
(xprin f-original "world2\n")
(:close f-original)
(xprin g-original "abc\n")
(:close g-original)
(assert (deep= @"hello\nworld2\n" (slurp "tmp/out.txt")) "file threading 1")
(assert (deep= @"world1\nabc\n" (slurp "tmp/out2.txt")) "file threading 2")
(end-suite)