stdio: implement tempnam() and tmpnam()
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+67
-7
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user