rktio: normalize 'replace and 'truncate/replace behavior

For `open-output-file`:

Move 'replace mode handling out of rktio and into the client.
Formerly, on Windows, 'replace mode was just 'truncate with a fallback
for permission problems, so this change makes it delete an existing
file and replace it, which is more consistent with how 'replace has
always worked on Unix.

In 'truncate/replace mode, if a truncating open fails due to a
permission error and the file exists, then try again as a 'replace.
That's how it worked on Windows before, and now it's how Unix works.
This commit is contained in:
Matthew Flatt 2017-06-18 05:43:55 -06:00
parent 5d68ec297a
commit 36ca1361d9
7 changed files with 90 additions and 86 deletions

View File

@ -3343,7 +3343,7 @@ static char *filename_for_error(Scheme_Object *p)
0);
}
static int can_enable_write_permission()
int scheme_can_enable_write_permission(void)
{
#ifdef DOS_FILE_SYSTEM
if (SCHEME_TRUEP(scheme_get_param(scheme_current_config(), MZCONFIG_FORCE_DELETE_PERMS)))
@ -3364,7 +3364,7 @@ static Scheme_Object *delete_file(int argc, Scheme_Object **argv)
NULL,
SCHEME_GUARD_FILE_DELETE);
if (!rktio_delete_file(scheme_rktio, fn, can_enable_write_permission())) {
if (!rktio_delete_file(scheme_rktio, fn, scheme_can_enable_write_permission())) {
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM,
"delete-file: cannot delete file\n"
" path: %q\n"
@ -4683,7 +4683,7 @@ static Scheme_Object *delete_directory(int argc, Scheme_Object *argv[])
#endif
if (!rktio_delete_directory(scheme_rktio, filename, current_directory,
can_enable_write_permission())) {
scheme_can_enable_write_permission())) {
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM,
"delete-directory: cannot delete directory\n"
" path: %q\n"

View File

@ -3595,7 +3595,7 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
int internal)
{
int e_set = 0, m_set = 0, i;
int existsok = 0;
int open_flags = 0, try_replace = 0;
char *filename;
char mode[4];
int typepos;
@ -3616,22 +3616,23 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
if (SAME_OBJ(argv[i], append_symbol)) {
mode[0] = 'a';
existsok = RKTIO_OPEN_APPEND;
open_flags = RKTIO_OPEN_APPEND;
e_set++;
} else if (SAME_OBJ(argv[i], replace_symbol)) {
existsok = RKTIO_OPEN_REPLACE;
try_replace = 1;
e_set++;
} else if (SAME_OBJ(argv[i], truncate_symbol)) {
existsok = RKTIO_OPEN_TRUNCATE | RKTIO_OPEN_CAN_EXIST;
open_flags = RKTIO_OPEN_TRUNCATE | RKTIO_OPEN_CAN_EXIST;
e_set++;
} else if (SAME_OBJ(argv[i], must_truncate_symbol)) {
existsok = RKTIO_OPEN_MUST_EXIST | RKTIO_OPEN_TRUNCATE;
open_flags = RKTIO_OPEN_MUST_EXIST | RKTIO_OPEN_TRUNCATE;
e_set++;
} else if (SAME_OBJ(argv[i], truncate_replace_symbol)) {
existsok = RKTIO_OPEN_TRUNCATE | RKTIO_OPEN_REPLACE | RKTIO_OPEN_CAN_EXIST;
open_flags = RKTIO_OPEN_TRUNCATE | RKTIO_OPEN_CAN_EXIST;
try_replace = 1;
e_set++;
} else if (SAME_OBJ(argv[i], update_symbol)) {
existsok = RKTIO_OPEN_MUST_EXIST;
open_flags = RKTIO_OPEN_MUST_EXIST;
if (typepos == 1) {
mode[2] = mode[1];
typepos = 2;
@ -3640,7 +3641,7 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
mode[1] = '+';
e_set++;
} else if (SAME_OBJ(argv[i], can_update_symbol)) {
existsok = RKTIO_OPEN_CAN_EXIST;
open_flags = RKTIO_OPEN_CAN_EXIST;
if (typepos == 1) {
mode[2] = mode[1];
typepos = 2;
@ -3686,7 +3687,7 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
(internal
? 0
: (SCHEME_GUARD_FILE_WRITE
| ((existsok & RKTIO_OPEN_REPLACE)
| (try_replace
? SCHEME_GUARD_FILE_DELETE
: 0)
/* append mode: */
@ -3694,19 +3695,38 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
? SCHEME_GUARD_FILE_READ
: 0)
/* update mode: */
| ((existsok & (RKTIO_OPEN_CAN_EXIST | RKTIO_OPEN_MUST_EXIST)
&& !(existsok & (RKTIO_OPEN_REPLACE
| RKTIO_OPEN_TRUNCATE
| RKTIO_OPEN_APPEND)))
| ((open_flags & (RKTIO_OPEN_CAN_EXIST | RKTIO_OPEN_MUST_EXIST)
&& !(open_flags & (RKTIO_OPEN_TRUNCATE
| RKTIO_OPEN_APPEND))
&& !try_replace)
? SCHEME_GUARD_FILE_READ
: 0))));
scheme_custodian_check_available(NULL, name, "file-stream");
fd = rktio_open(scheme_rktio, filename, (RKTIO_OPEN_WRITE
| existsok
| (and_read ? RKTIO_OPEN_READ : 0)
| ((mode[1] == 't') ? RKTIO_OPEN_TEXT : 0)));
while (1) {
fd = rktio_open(scheme_rktio, filename, (RKTIO_OPEN_WRITE
| open_flags
| (and_read ? RKTIO_OPEN_READ : 0)
| ((mode[1] == 't') ? RKTIO_OPEN_TEXT : 0)));
if (!fd
&& try_replace
&& (scheme_last_error_is_racket(RKTIO_ERROR_EXISTS)
|| (scheme_last_error_is_racket(RKTIO_ERROR_ACCESS_DENIED)
&& rktio_file_exists(scheme_rktio, filename)))) {
/* In replace mode, delete file and try again */
if (!rktio_delete_file(scheme_rktio, filename, scheme_can_enable_write_permission())) {
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM,
"%s: error deleting file\n"
" path: %q\n"
" system error: %R",
name, filename);
}
try_replace = 0;
} else
break;
}
if (!fd) {
if (scheme_last_error_is_racket(RKTIO_ERROR_EXISTS)) {
@ -3718,19 +3738,12 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
"%s: path is a directory\n"
" path: %q",
name, filename);
} else {
#if 0
/* Add a way to get this information from rktio_open()? */
if (....)
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM,
"%s: error deleting file\n"
} else
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM,
"%s: cannot open output file\n"
" path: %q\n"
" system error: %R",
name, filename);
#endif
filename_exn(name, "cannot open output file", filename, 0);
}
name, filename);
}
return make_fd_output_port(fd, scheme_make_path(filename), and_read, -1, NULL);

View File

@ -4549,6 +4549,8 @@ Scheme_Object *scheme_current_library_collection_paths(int argc, Scheme_Object *
Scheme_Object *scheme_current_library_collection_links(int argc, Scheme_Object *argv[]);
Scheme_Object *scheme_compiled_file_roots(int argc, Scheme_Object *argv[]);
int scheme_can_enable_write_permission(void);
#ifdef MZ_USE_JIT
int scheme_can_inline_fp_op();
int scheme_can_inline_fp_comp();

View File

@ -704,7 +704,7 @@ int main(int argc, char **argv)
rktio_fd_t **pipe_fds;
if (stress && verbose)
printf(" iter %d\n", i);
printf(" iter %d\n", (int)i);
pipe_fds = rktio_make_pipe(rktio, 0);
check_valid(pipe_fds);
@ -756,7 +756,7 @@ int main(int argc, char **argv)
rktio_listener_t *lnr;
if (stress && verbose)
printf(" iter %d\n", i);
printf(" iter %d\n", (int)i);
check_many_lookup(rktio);

View File

@ -112,24 +112,23 @@ typedef struct rktio_fd_t rktio_fd_t;
/* Used for `rktio_open` with `RKTIO_OPEN_WRITE`: */
#define RKTIO_OPEN_TRUNCATE (1<<3)
#define RKTIO_OPEN_APPEND (1<<4)
#define RKTIO_OPEN_REPLACE (1<<5)
#define RKTIO_OPEN_MUST_EXIST (1<<6)
#define RKTIO_OPEN_CAN_EXIST (1<<7)
#define RKTIO_OPEN_MUST_EXIST (1<<5)
#define RKTIO_OPEN_CAN_EXIST (1<<6)
/* Used for `rktio_system_fd`: */
#define RKTIO_OPEN_SOCKET (1<<8)
#define RKTIO_OPEN_UDP (1<<9)
#define RKTIO_OPEN_REGFILE (1<<10)
#define RKTIO_OPEN_NOT_REGFILE (1<<11)
#define RKTIO_OPEN_SOCKET (1<<7)
#define RKTIO_OPEN_UDP (1<<8)
#define RKTIO_OPEN_REGFILE (1<<9)
#define RKTIO_OPEN_NOT_REGFILE (1<<10)
/* If neither RKTIO_OPEN_REGILE nor RKTIO_OPEN_NOT_REGILE
are specified, then the value is inferred by `rtkio_system_fd`. */
#define RKTIO_OPEN_DIR (1<<12)
#define RKTIO_OPEN_NOT_DIR (1<<13)
#define RKTIO_OPEN_DIR (1<<11)
#define RKTIO_OPEN_NOT_DIR (1<<12)
/* Inferred when neither is specified and when `RKTIO_OPEN_[NOT_]REGFILE`
is also inferred. */
#define RKTIO_OPEN_INIT (1<<14)
#define RKTIO_OPEN_INIT (1<<13)
/* Make `rtkio_system_fd` set a socket as nonblocking, etc. */
#define RKTIO_OPEN_OWN (1<<15)
#define RKTIO_OPEN_OWN (1<<14)
/* Make `rtkio_system_fd` record a socket for reliable clean up on pre-NT Windows. */
RKTIO_EXTERN rktio_fd_t *rktio_system_fd(rktio_t *rktio, intptr_t system_fd, int modes);
@ -152,8 +151,11 @@ RKTIO_EXTERN int rktio_fd_modes(rktio_t *rktio, rktio_fd_t *rfd);
/* Returns all of the recorded mode flags. */
RKTIO_EXTERN rktio_fd_t *rktio_open(rktio_t *rktio, const char *src, int modes);
/* Can report `RKTIO_ERROR_DOES_NOT_EXIST` in place of system error,
and can report `RKTIO_ERROR_UNSUPPORTED_TEXT_MODE` on Windows. */
/* Can report `RKTIO_ERROR_DOES_NOT_EXIST` in place of a system error
in read mode, and can report `RKTIO_ERROR_IS_A_DIRECTORY`,
`RKTIO_ERROR_EXISTS`, or `RKTIO_ERROR_ACCESS_DENIED` in place of a
system error in write mode. On Windows, can report
`RKTIO_ERROR_UNSUPPORTED_TEXT_MODE`. */
RKTIO_EXTERN rktio_ok_t rktio_close(rktio_t *rktio, rktio_fd_t *fd);
/* Can report `RKTIO_ERROR_EXISTS` in place of system error,
@ -823,6 +825,7 @@ enum {
RKTIO_ERROR_UNSUPPORTED = 1,
RKTIO_ERROR_DOES_NOT_EXIST,
RKTIO_ERROR_EXISTS,
RKTIO_ERROR_ACCESS_DENIED,
RKTIO_ERROR_LINK_FAILED,
RKTIO_ERROR_NOT_A_LINK,
RKTIO_ERROR_BAD_PERMISSION,

View File

@ -91,6 +91,22 @@ void rktio_remap_last_error(rktio_t *rktio)
#endif
#ifdef RKTIO_SYSTEM_WINDOWS
rktio_set_last_error(rktio, RKTIO_ERROR_KIND_WINDOWS, ERROR_FILE_NOT_FOUND);
#endif
break;
case RKTIO_ERROR_EXISTS:
#ifdef RKTIO_SYSTEM_UNIX
rktio_set_last_error(rktio, RKTIO_ERROR_KIND_POSIX, EEXIST);
#endif
#ifdef RKTIO_SYSTEM_WINDOWS
rktio_set_last_error(rktio, RKTIO_ERROR_KIND_WINDOWS, RKTIO_ERROR_EXISTS);
#endif
break;
case RKTIO_ERROR_ACCESS_DENIED:
#ifdef RKTIO_SYSTEM_UNIX
rktio_set_last_error(rktio, RKTIO_ERROR_KIND_POSIX, EACCES);
#endif
#ifdef RKTIO_SYSTEM_WINDOWS
rktio_set_last_error(rktio, RKTIO_ERROR_KIND_WINDOWS, ERROR_ACCESS_DENIED);
#endif
break;
}

View File

@ -131,23 +131,11 @@ static rktio_fd_t *open_write(rktio_t *rktio, const char *filename, int modes)
set_racket_error(RKTIO_ERROR_IS_A_DIRECTORY);
return NULL;
} else if (errno == EEXIST) {
if (!(modes & RKTIO_OPEN_REPLACE)) {
set_racket_error(RKTIO_ERROR_EXISTS);
return NULL;
} else {
do {
cr = unlink(filename);
} while ((cr == -1) && (errno == EINTR));
if (cr) {
get_posix_error();
return NULL;
}
do {
fd = open(filename, flags | RKTIO_BINARY, 0666);
} while ((fd == -1) && (errno == EINTR));
}
set_racket_error(RKTIO_ERROR_EXISTS);
return NULL;
} else if (errno == EACCES) {
set_racket_error(RKTIO_ERROR_ACCESS_DENIED);
return NULL;
}
if (fd == -1) {
@ -197,32 +185,14 @@ static rktio_fd_t *open_write(rktio_t *rktio, const char *filename, int modes)
NULL);
if (fd == INVALID_HANDLE_VALUE) {
int errv;
errv = GetLastError();
if ((errv == ERROR_ACCESS_DENIED) && (modes & RKTIO_OPEN_REPLACE)) {
/* Delete and try again... */
if (DeleteFileW(WIDE_PATH_temp(filename))) {
fd = CreateFileW(WIDE_PATH_temp(filename),
GENERIC_WRITE | ((modes & RKTIO_OPEN_READ) ? GENERIC_READ : 0),
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
hmode,
0,
NULL);
if (fd == INVALID_HANDLE_VALUE) {
get_windows_error();
return NULL;
}
} else {
get_windows_error();
return NULL;
}
int errv = GetLastError();
if (errv == ERROR_ACCESS_DENIED) {
set_racket_error(RKTIO_ERROR_ACCESS_DENIED);
return NULL;
} else if (errv == ERROR_FILE_EXISTS) {
set_racket_error(RKTIO_ERROR_EXISTS);
return NULL;
}
if (fd == INVALID_HANDLE_VALUE) {
} else {
get_windows_error();
return NULL;
}