Browse Source

stdio: implement tempnam() and tmpnam()

Alex Lyon 5 years ago
parent
commit
3584edf199
6 changed files with 250 additions and 7 deletions
  1. 3 0
      include/bits/stdio.h
  2. 9 0
      src/header/stdio/constants.rs
  3. 67 7
      src/header/stdio/mod.rs
  4. 3 0
      tests/Makefile
  5. 127 0
      tests/stdio/tempnam.c
  6. 41 0
      tests/stdio/tmpnam.c

+ 3 - 0
include/bits/stdio.h

@@ -1,6 +1,9 @@
 #ifndef _BITS_STDIO_H
 #ifndef _BITS_STDIO_H
 #define _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)
 #define EOF (-1)
 
 
 typedef struct FILE FILE;
 typedef struct FILE FILE;

+ 9 - 0
src/header/stdio/constants.rs

@@ -24,5 +24,14 @@ pub const _IOFBF: c_int = 0;
 pub const _IOLBF: c_int = 1;
 pub const _IOLBF: c_int = 1;
 pub const _IONBF: c_int = 2;
 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)]
 #[allow(non_camel_case_types)]
 pub type fpos_t = off_t;
 pub type fpos_t = off_t;

+ 67 - 7
src/header/stdio/mod.rs

@@ -11,7 +11,7 @@ use core::{fmt, mem, ptr, slice, str};
 use c_str::CStr;
 use c_str::CStr;
 use fs::File;
 use fs::File;
 use header::errno::{self, STR_ERROR};
 use header::errno::{self, STR_ERROR};
-use header::string::strlen;
+use header::string::{self, strlen};
 use header::{fcntl, stdlib, unistd};
 use header::{fcntl, stdlib, unistd};
 use io::{self, BufRead, LineWriter, Read, Write};
 use io::{self, BufRead, LineWriter, Read, Write};
 use mutex::Mutex;
 use mutex::Mutex;
@@ -34,6 +34,8 @@ mod helpers;
 mod printf;
 mod printf;
 mod scanf;
 mod scanf;
 
 
+static mut TMPNAM_BUF: [c_char; L_tmpnam as usize + 1] = [0; L_tmpnam as usize + 1];
+
 enum Buffer<'a> {
 enum Buffer<'a> {
     Borrowed(&'a mut [u8]),
     Borrowed(&'a mut [u8]),
     Owned(Vec<u8>),
     Owned(Vec<u8>),
@@ -873,9 +875,44 @@ pub unsafe extern "C" fn setvbuf(
     0
     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]
 #[no_mangle]
@@ -901,9 +938,32 @@ pub unsafe extern "C" fn tmpfile() -> *mut FILE {
     fp
     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
 /// Push character `c` back onto `stream` so it'll be read next

+ 3 - 0
tests/Makefile

@@ -52,6 +52,7 @@ EXPECT_BINS=\
 	string/strchr \
 	string/strchr \
 	string/strcpy \
 	string/strcpy \
 	string/strcspn \
 	string/strcspn \
+	string/strlen \
 	string/strncmp \
 	string/strncmp \
 	string/strpbrk \
 	string/strpbrk \
 	string/strrchr \
 	string/strrchr \
@@ -96,6 +97,8 @@ BINS=\
 	$(EXPECT_BINS) \
 	$(EXPECT_BINS) \
 	dirent/main \
 	dirent/main \
 	pwd \
 	pwd \
+	stdio/tempnam \
+	stdio/tmpnam \
 	stdlib/alloc \
 	stdlib/alloc \
 	stdlib/bsearch \
 	stdlib/bsearch \
 	stdlib/mktemp \
 	stdlib/mktemp \

+ 127 - 0
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);
+}
+

+ 41 - 0
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;
+}