diff --git a/include/bits/stdio.h b/include/bits/stdio.h index 428cded594f09c14353ee36e8db6b222879e91a2..97c6c1036b57fecfd375ce52e76d5f2e49e5d90a 100644 --- a/include/bits/stdio.h +++ b/include/bits/stdio.h @@ -1,6 +1,9 @@ #ifndef _BITS_STDIO_H #define _BITS_STDIO_H +// XXX: this is only here because cbindgen can't handle string constants +#define P_tmpdir "/tmp" + #define EOF (-1) typedef struct FILE FILE; diff --git a/src/header/stdio/constants.rs b/src/header/stdio/constants.rs index 2adea19182e650b87265dbf2b69de99808d44d1f..eef8f3fe0e1f49fb2494957a6c1aab6c685a2e86 100644 --- a/src/header/stdio/constants.rs +++ b/src/header/stdio/constants.rs @@ -24,5 +24,14 @@ pub const _IOFBF: c_int = 0; pub const _IOLBF: c_int = 1; pub const _IONBF: c_int = 2; +// form of name is /XXXXXX, so 7 +pub const L_tmpnam: c_int = 7; +// 36^6 (26 letters + 10 digits) is larger than i32::MAX, so just set to that +// for now +pub const TMP_MAX: int32_t = 2_147_483_647; +// XXX: defined manually in bits/stdio.h as well because cbindgen can't handle +// string constants in any form AFAICT +pub const P_tmpdir: &'static [u8; 5] = b"/tmp\0"; + #[allow(non_camel_case_types)] pub type fpos_t = off_t; diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs index bda7802055cb9d2282c722215f7f8eab46e0441c..3af7ee3d673c22804e98baa7dd94e94ce201f060 100644 --- a/src/header/stdio/mod.rs +++ b/src/header/stdio/mod.rs @@ -11,7 +11,7 @@ use core::{fmt, mem, ptr, slice, str}; use c_str::CStr; use fs::File; use header::errno::{self, STR_ERROR}; -use header::string::strlen; +use header::string::{self, strlen}; use header::{fcntl, stdlib, unistd}; use io::{self, BufRead, LineWriter, Read, Write}; use mutex::Mutex; @@ -34,6 +34,8 @@ mod helpers; mod printf; mod scanf; +static mut TMPNAM_BUF: [c_char; L_tmpnam as usize + 1] = [0; L_tmpnam as usize + 1]; + enum Buffer<'a> { Borrowed(&'a mut [u8]), Owned(Vec<u8>), @@ -873,9 +875,44 @@ pub unsafe extern "C" fn setvbuf( 0 } -// #[no_mangle] -pub extern "C" fn tempnam(_dir: *const c_char, _pfx: *const c_char) -> *mut c_char { - unimplemented!(); +#[no_mangle] +pub unsafe extern "C" fn tempnam(dir: *const c_char, pfx: *const c_char) -> *mut c_char { + unsafe fn is_appropriate(pos_dir: *const c_char) -> bool { + !pos_dir.is_null() && unistd::access(pos_dir, unistd::W_OK) == 0 + } + + // directory search order is env!(TMPDIR), dir, P_tmpdir, "/tmp" + let dirname = { + let tmpdir = stdlib::getenv(b"TMPDIR\0".as_ptr() as _); + [tmpdir, dir, P_tmpdir.as_ptr() as _] + .into_iter() + .map(|&d| d) + .skip_while(|&d| !is_appropriate(d)) + .next() + .unwrap_or(b"/tmp\0".as_ptr() as _) + }; + let dirname_len = string::strlen(dirname); + + let prefix_len = string::strnlen_s(pfx, 5); + + // allocate enough for dirname "/" prefix "XXXXXX\0" + let mut out_buf = platform::alloc(dirname_len + 1 + prefix_len + L_tmpnam as usize + 1) as *mut c_char; + + if !out_buf.is_null() { + // copy the directory name and prefix into the allocated buffer + out_buf.copy_from_nonoverlapping(dirname, dirname_len); + *out_buf.add(dirname_len) = b'/' as _; + out_buf.add(dirname_len + 1).copy_from_nonoverlapping(pfx, prefix_len); + + // use the same mechanism as tmpnam to get the file name + if tmpnam_inner(out_buf, dirname_len + 1 + prefix_len).is_null() { + // failed to find a valid file name, so we need to free the buffer + platform::free(out_buf as _); + out_buf = ptr::null_mut(); + } + } + + out_buf } #[no_mangle] @@ -901,9 +938,32 @@ pub unsafe extern "C" fn tmpfile() -> *mut FILE { fp } -// #[no_mangle] -pub extern "C" fn tmpnam(_s: *mut c_char) -> *mut c_char { - unimplemented!(); +#[no_mangle] +pub unsafe extern "C" fn tmpnam(s: *mut c_char) -> *mut c_char { + let buf = if s.is_null() { + TMPNAM_BUF.as_mut_ptr() + } else { + s + }; + + *buf = b'/' as _; + tmpnam_inner(buf, 1) +} + +unsafe extern "C" fn tmpnam_inner(buf: *mut c_char, offset: usize) -> *mut c_char { + const TEMPLATE: &[u8] = b"XXXXXX\0"; + + buf.add(offset).copy_from_nonoverlapping(TEMPLATE.as_ptr() as _, TEMPLATE.len()); + + let err = platform::errno; + stdlib::mktemp(buf); + platform::errno = err; + + if *buf == 0 { + ptr::null_mut() + } else { + buf + } } /// Push character `c` back onto `stream` so it'll be read next diff --git a/tests/Makefile b/tests/Makefile index 7b1f946fbcf21fb5640a1f7eab19701ff5b2d8f0..2f967511f9409605ae09c8d215bfdf4e5bf10c35 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -52,6 +52,7 @@ EXPECT_BINS=\ string/strchr \ string/strcpy \ string/strcspn \ + string/strlen \ string/strncmp \ string/strpbrk \ string/strrchr \ @@ -96,6 +97,8 @@ BINS=\ $(EXPECT_BINS) \ dirent/main \ pwd \ + stdio/tempnam \ + stdio/tmpnam \ stdlib/alloc \ stdlib/bsearch \ stdlib/mktemp \ diff --git a/tests/stdio/tempnam.c b/tests/stdio/tempnam.c new file mode 100644 index 0000000000000000000000000000000000000000..8fd7edf6d0dfeac4433f31d3dec88be49b96d49a --- /dev/null +++ b/tests/stdio/tempnam.c @@ -0,0 +1,127 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "test_helpers.h" + +static void test_prefix(const char *prefix); +static void test_dir(const char *dir); +static void test_dir_and_prefix(const char *dir, const char *prefix); + +int main(void) { + char *first_null = tempnam(NULL, NULL); + if(first_null == NULL) { + // NOTE: assuming that we can at least get one file name + puts("tempnam(NULL, NULL) returned NULL on first try"); + exit(EXIT_FAILURE); + } + printf("%s\n", first_null); + + char *second_null = tempnam(NULL, NULL); + if(second_null == NULL) { + // NOTE: assuming that we can at least get one file name + puts("tempnam(NULL, NULL) returned NULL on second try"); + free(first_null); + exit(EXIT_FAILURE); + } + printf("%s\n", second_null); + + free(first_null); + free(second_null); + + if(first_null == second_null) { + puts("tempnam(NULL, NULL) returns the same address"); + exit(EXIT_FAILURE); + } + + // Ensure the "prefix" argument works + test_prefix("this_is_a_test_prefix"); + test_prefix("exact"); + test_prefix("hi"); + test_prefix(""); + + // Ensure the "dir" argument works + + // NOTE: needed because TMPDIR is the first directory checked + unsetenv("TMPDIR"); + + test_dir("/tmp"); + test_dir(""); + // NOTE: assumes /root is NOT writable + test_dir("/root"); + + // Ensure "prefix" and "dir" work together + test_dir_and_prefix("/tmp", "this_is_a_prefix"); + test_dir_and_prefix("/tmp", "exact"); + test_dir_and_prefix("/root", "exact"); + test_dir_and_prefix("/root", "long_prefix"); + test_dir_and_prefix("", "prefix"); + test_dir_and_prefix("/tmp", "test"); + + return 0; +} + +static void test_prefix(const char *prefix) { + test_dir_and_prefix(NULL, prefix); +} + +static void test_dir(const char *dir) { + test_dir_and_prefix(dir, NULL); +} + +static void test_dir_and_prefix(const char *dir, const char *prefix) { + char funcbuf[256]; + if(dir && prefix) { + snprintf(funcbuf, sizeof(funcbuf), "tempnam(\"%s\", \"%s\")", dir, prefix); + } else if(dir) { + snprintf(funcbuf, sizeof(funcbuf), "tempnam(\"%s\", NULL)", dir); + } else if(prefix) { + snprintf(funcbuf, sizeof(funcbuf), "tempnam(NULL, \"%s\")", prefix); + } else { + strcpy(funcbuf, "tempnam(NULL, NULL)"); + } + + char *result = tempnam(dir, prefix); + if(!result) { + printf("%s failed\n", funcbuf); + exit(EXIT_FAILURE); + } + printf("%s\n", result); + + if(prefix) { + char buf[7] = { '/' }; + strncpy(&buf[1], prefix, sizeof(buf) - 2); + buf[6] = 0; + + char *prev = NULL; + char *substr = result; + do { + prev = substr; + substr = strstr(&substr[1], buf); + } while(substr); + substr = prev; + + if(!substr) { + printf("%s did not add the full (5 bytes at most) prefix\n", funcbuf); + free(result); + exit(EXIT_FAILURE); + } else if(strlen(substr) != strlen(&buf[1]) + L_tmpnam) { + printf("%s has the wrong length\n", funcbuf); + free(result); + exit(EXIT_FAILURE); + } + } + + if(dir) { + char *substr = strstr(result, dir); + char *other_substr = strstr(result, P_tmpdir); + if(!substr && !other_substr) { + printf("%s is in an unexpected directory\n", funcbuf); + free(result); + exit(EXIT_FAILURE); + } + } + + free(result); +} + diff --git a/tests/stdio/tmpnam.c b/tests/stdio/tmpnam.c new file mode 100644 index 0000000000000000000000000000000000000000..02c07d6724b3ccc64c9baf311d0d82e094950a4a --- /dev/null +++ b/tests/stdio/tmpnam.c @@ -0,0 +1,41 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "test_helpers.h" + +int main(void) { + char *first_null = tmpnam(NULL); + if(first_null == NULL) { + // NOTE: assuming that we can at least get one file name + puts("tmpnam(NULL) returned NULL on first try"); + exit(EXIT_FAILURE); + } + printf("%s\n", first_null); + + char *second_null = tmpnam(NULL); + if(second_null == NULL) { + // NOTE: assuming that we can at least get one file name + puts("tmpnam(NULL) returned NULL on second try"); + exit(EXIT_FAILURE); + } + printf("%s\n", second_null); + + if(first_null != second_null) { + puts("tmpnam(NULL) returns different addresses"); + exit(EXIT_FAILURE); + } + + char buffer[L_tmpnam + 1]; + char *buf_result = tmpnam(buffer); + if(buf_result == NULL) { + puts("tmpnam(buffer) failed"); + exit(EXIT_FAILURE); + } else if(buf_result != buffer) { + puts("tmpnam(buffer) did not return buffer's address"); + exit(EXIT_FAILURE); + } + printf("%s\n", buffer); + + return 0; +}