fix problems in copy-file'; add exists-ok?' argument

The non-Windows `copy-file' had a race condition, and it could
behave in bad ways if the source of a copy is not a regular
file.
This commit is contained in:
Matthew Flatt 2011-09-19 08:11:17 -06:00
parent 7513c027c9
commit 7f887fdf60
6 changed files with 187 additions and 108 deletions

View File

@ -360,14 +360,18 @@ OS X, this size excludes the resource-fork size. On error (e.g., if no
such file exists), the @exnraise[exn:fail:filesystem].}
@defproc[(copy-file [src path-string?] [dest path-string?]) void?]{
@defproc[(copy-file [src path-string?] [dest path-string?] [exists-ok? any/c #f]) void?]{
Creates the file @racket[dest] as a copy of @racket[src], if
@racket[dest] does not already exist. If @racket[dest] already exists
and @racket[exists-ok?] is @racket[#f], the copy fails with
@exnraise[exn:fail:filesystem:exists?]; otherwise, if @racket[dest]
exists, its content is replaced with the content of @racket[src]. File
permissions are transferred from @racket[src] to @racket[dest]. If
@racket[src] refers to a link, the target of the link is copied,
rather than the link itself; if @racket[dest] refers to a link and
@racket[exists-ok?] is true, the target of the link is updated.}
Creates the file @racket[dest] as a copy of @racket[src]. If the file
is not successfully copied, the @exnraise[exn:fail:filesystem]. If
@racket[dest] already exists, the copy will fail. File permissions are
preserved in the copy. On Mac OS X, the resource fork is also
preserved in the copy. If @racket[src] refers to a link, the target of
the link is copied, rather than the link itself.}
@defproc[(make-file-or-directory-link [to path-string?] [path path-string?])
void?]{

View File

@ -405,7 +405,7 @@ void scheme_init_file(Scheme_Env *env)
scheme_add_global_constant("copy-file",
scheme_make_prim_w_arity(copy_file,
"copy-file",
2, 2),
2, 3),
env);
scheme_add_global_constant("build-path",
scheme_make_prim_w_arity(scheme_build_path,
@ -3849,7 +3849,7 @@ failed:
static Scheme_Object *copy_file(int argc, Scheme_Object **argv)
{
char *src, *dest, *reason = NULL;
int pre_exists = 0, has_err_val = 0, err_val = 0;
int pre_exists = 0, has_err_val = 0, err_val = 0, exists_ok = 0;
Scheme_Object *bss, *bsd;
if (!SCHEME_PATH_STRINGP(argv[0]))
@ -3859,6 +3859,7 @@ static Scheme_Object *copy_file(int argc, Scheme_Object **argv)
bss = argv[0];
bsd = argv[1];
exists_ok = ((argc > 2) && SCHEME_TRUEP(argv[2]));
src = scheme_expand_string_filename(bss,
"copy-file",
@ -3872,79 +3873,89 @@ static Scheme_Object *copy_file(int argc, Scheme_Object **argv)
#ifdef UNIX_FILE_SYSTEM
{
# define COPY_BUFFER_SIZE 2048
FILE *s, *d;
char b[COPY_BUFFER_SIZE];
intptr_t len;
int ok;
struct stat buf;
mz_jmp_buf newbuf, * volatile savebuf;
int a_cnt;
Scheme_Object *a[2], * volatile in, * volatile out;
reason = NULL;
in = scheme_do_open_input_file("copy-file", 0, 1, argv, 1, &reason, &err_val);
if (!in) {
has_err_val = !!err_val;
goto failed;
}
do {
ok = stat(src, &buf);
ok = fstat(scheme_get_port_fd(in), &buf);
} while ((ok == -1) && (errno == EINTR));
if (ok || S_ISDIR(buf.st_mode)) {
reason = "source file does not exist";
goto failed;
}
do {
ok = stat(dest, &buf);
} while ((ok == -1) && (errno == EINTR));
if (!ok) {
reason = "destination already exists";
pre_exists = 1;
goto failed;
}
s = fopen(src, "rb");
if (!s) {
reason = "error getting mode";
err_val = errno;
has_err_val = 1;
reason = "cannot open source file";
goto failed;
}
d = fopen(dest, "wb");
if (!d) {
err_val = errno;
has_err_val = 1;
fclose(s);
reason = "cannot open destination file";
a[0] = argv[1];
if (exists_ok) {
a_cnt = 2;
a[1] = scheme_intern_symbol("truncate");
} else
a_cnt = 1;
out = scheme_do_open_output_file("copy-file", 0, a_cnt, a, 0, 1, &reason, &err_val);
if (!out) {
scheme_close_input_port(in);
has_err_val = !!err_val;
goto failed;
}
ok = 1;
while ((len = fread(b, 1, COPY_BUFFER_SIZE, s))) {
if (fwrite(b, 1, len, d) != len) {
ok = 0;
break;
/* catch errors or breaks during read and write to close ports: */
savebuf = scheme_current_thread->error_buf;
scheme_current_thread->error_buf = &newbuf;
if (scheme_setjmp(newbuf)) {
scheme_close_input_port(in);
scheme_close_output_port(out);
scheme_current_thread->error_buf = savebuf;
scheme_longjmp(*savebuf, 1);
return NULL;
} else {
ok = 1;
while ((len = scheme_get_byte_string("copy-file", in, b, 0, COPY_BUFFER_SIZE, 0, 0, NULL))) {
if (len == -1)
break;
if (scheme_put_byte_string("copy-file", out, b, 0, len, 0) != len) {
ok = 0;
break;
}
}
}
if (!feof(s))
ok = 0;
fclose(s);
fclose(d);
scheme_current_thread->error_buf = savebuf;
if (ok) {
while (1) {
if (!chmod(dest, buf.st_mode))
return scheme_void;
else if (errno != EINTR)
break;
do {
err_val = fchmod(scheme_get_port_fd(out), buf.st_mode);
} while ((err_val == -1) && (errno != EINTR));
if (err_val) {
err_val = errno;
has_err_val = 0;
reason = "cannot set destination's mode";
ok = 0;
}
err_val = errno;
has_err_val = 0;
reason = "cannot set destination's mode";
} else
reason = "read or write failed";
scheme_close_input_port(in);
scheme_close_output_port(out);
if (ok)
return scheme_void;
}
failed:
#endif
#ifdef DOS_FILE_SYSTEM
if (CopyFileW(WIDE_PATH_COPY(src), WIDE_PATH(dest), TRUE))
if (CopyFileW(WIDE_PATH_COPY(src), WIDE_PATH(dest), !exists_ok))
return scheme_void;
reason = "copy failed";

View File

@ -4014,7 +4014,8 @@ static void filename_exn(char *name, char *msg, char *filename, int err)
}
Scheme_Object *
scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[], int internal)
scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[],
int internal, char **err, int *eerrno)
{
#ifdef USE_FD_PORTS
int fd;
@ -4083,7 +4084,11 @@ scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[
} while ((fd == -1) && (errno == EINTR));
if (fd == -1) {
filename_exn(name, "cannot open input file", filename, errno);
if (err) {
*err = "cannot open source file";
*eerrno = errno;
} else
filename_exn(name, "cannot open input file", filename, errno);
return NULL;
} else {
int ok;
@ -4097,7 +4102,11 @@ scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[
do {
cr = close(fd);
} while ((cr == -1) && (errno == EINTR));
filename_exn(name, "cannot open directory as a file", filename, 0);
if (err) {
*err = "source is a directory";
*eerrno = 0;
} else
filename_exn(name, "cannot open directory as a file", filename, 0);
return NULL;
} else {
regfile = S_ISREG(buf.st_mode);
@ -4115,7 +4124,11 @@ scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[
NULL);
if (fd == INVALID_HANDLE_VALUE) {
filename_exn(name, "cannot open input file", filename, GetLastError());
if (err) {
*err = "cannot open source file";
*eerrno = GetLastError();
} else
filename_exn(name, "cannot open input file", filename, GetLastError());
return NULL;
} else
regfile = (GetFileType(fd) == FILE_TYPE_DISK);
@ -4129,7 +4142,11 @@ scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[
result = make_fd_input_port((int)fd, scheme_make_path(filename), regfile, mode[1] == 't', NULL, internal);
# else
if (scheme_directory_exists(filename)) {
filename_exn(name, "cannot open directory as a file", filename, 0);
if (err) {
*err = "source is a directory";
*eerrno = 0;
} else
filename_exn(name, err, filename, 0);
return NULL;
}
@ -4137,7 +4154,11 @@ scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[
fp = fopen(filename, mode);
if (!fp) {
filename_exn(name, "cannot open input file", filename, errno);
if (err) {
*err = "cannot open source file";
*eerrno = errno;
} else
filename_exn(name, "cannot open input file", filename, errno);
return NULL;
}
@ -4149,7 +4170,8 @@ scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[
}
Scheme_Object *
scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv[], int and_read)
scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv[], int and_read,
int internal, char **err, int *eerrno)
{
#ifdef USE_FD_PORTS
int fd;
@ -4254,18 +4276,20 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
filename = scheme_expand_string_filename(argv[0],
name, NULL,
(SCHEME_GUARD_FILE_WRITE
| ((existsok && ((existsok == 1) || (existsok == -2)))
? SCHEME_GUARD_FILE_DELETE
: 0)
/* append mode: */
| ((mode[0] == 'a')
? SCHEME_GUARD_FILE_READ
: 0)
/* update mode: */
| ((existsok > 1)
? SCHEME_GUARD_FILE_READ
: 0)));
(internal
? 0
: (SCHEME_GUARD_FILE_WRITE
| ((existsok && ((existsok == 1) || (existsok == -2)))
? SCHEME_GUARD_FILE_DELETE
: 0)
/* append mode: */
| ((mode[0] == 'a')
? SCHEME_GUARD_FILE_READ
: 0)
/* update mode: */
| ((existsok > 1)
? SCHEME_GUARD_FILE_READ
: 0))));
scheme_custodian_check_available(NULL, name, "file-stream");
@ -4297,14 +4321,24 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
if (fd == -1) {
if (errno == EISDIR) {
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: \"%q\" exists as a directory",
name, filename);
if (err) {
*err = "destination exists as a directory";
*eerrno = errno;
return NULL;
} else
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: \"%q\" exists as a directory",
name, filename);
} else if (errno == EEXIST) {
if (!existsok)
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: file \"%q\" exists", name, filename);
else {
if (!existsok) {
if (err) {
*err = "destination already exists";
*eerrno = errno;
return NULL;
} else
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: file \"%q\" exists", name, filename);
} else {
do {
ok = unlink(filename);
} while ((ok == -1) && (errno == EINTR));
@ -4320,8 +4354,12 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
}
if (fd == -1) {
filename_exn(name, "cannot open output file", filename, errno);
return NULL; /* shouldn't get here */
if (err) {
*err = "cannot open destination file";
*eerrno = errno;
} else
filename_exn(name, "cannot open output file", filename, errno);
return NULL;
}
}
@ -4380,13 +4418,21 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
return NULL;
}
} else if (err == ERROR_FILE_EXISTS) {
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: file \"%q\" exists", name, filename);
if (err) {
*err = "destination already exists";
*eerrno = err;
} else
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: file \"%q\" exists", name, filename);
return NULL;
}
if (fd == INVALID_HANDLE_VALUE) {
filename_exn(name, "cannot open output file", filename, err);
if (err) {
*err = "cannot open destination";
*eerrno = err;
} else
filename_exn(name, "cannot open output file", filename, err);
return NULL;
}
}
@ -4420,13 +4466,16 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
-1, NULL);
# else
if (scheme_directory_exists(filename)) {
if (!existsok)
if (err) {
*err = "destination exists as a directory";
*eerrno = 0;
} else if (!existsok)
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: \"%q\" exists as a directory",
name, filename);
else
filename_exn(name, "cannot open directory as a file", filename, errno);
return scheme_void;
return NULL;
}
@ -4440,9 +4489,16 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
if (scheme_file_exists(filename)) {
int uok;
if (!existsok)
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: file \"%q\" exists", name, filename);
if (!existsok) {
if (err) {
*err = "destination exists already";
*eerrno = 0;
} else
scheme_raise_exn(MZEXN_FAIL_FILESYSTEM_EXISTS,
"%s: file \"%q\" exists", name, filename);
return NULL;
}
do {
uok = MSC_IZE(unlink)(filename);
} while ((uok == -1) && (errno == EINTR));
@ -4473,8 +4529,14 @@ scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv
}
}
}
if (!fp)
filename_exn(name, "cannot open output file", filename, errno);
if (!fp) {
if (err) {
*err = "cannot open destination";
*eerrno = errno;
} else
filename_exn(name, "cannot open output file", filename, errno);
return NULL;
}
}
return scheme_make_file_output_port(fp);
@ -4487,7 +4549,7 @@ Scheme_Object *scheme_open_input_file(const char *name, const char *who)
Scheme_Object *a[1];
a[0]= scheme_make_path(name);
return scheme_do_open_input_file((char *)who, 0, 1, a, 0);
return scheme_do_open_input_file((char *)who, 0, 1, a, 0, NULL, NULL);
}
Scheme_Object *scheme_open_output_file(const char *name, const char *who)
@ -4496,7 +4558,7 @@ Scheme_Object *scheme_open_output_file(const char *name, const char *who)
a[0]= scheme_make_path(name);
a[1] = truncate_replace_symbol;
return scheme_do_open_output_file((char *)who, 0, 2, a, 0);
return scheme_do_open_output_file((char *)who, 0, 2, a, 0, 0, NULL, NULL);
}
Scheme_Object *scheme_open_input_output_file(const char *name, const char *who, Scheme_Object **oport)
@ -4505,7 +4567,7 @@ Scheme_Object *scheme_open_input_output_file(const char *name, const char *who,
a[0]= scheme_make_path(name);
a[1] = truncate_replace_symbol;
scheme_do_open_output_file((char *)who, 0, 2, a, 1);
scheme_do_open_output_file((char *)who, 0, 2, a, 1, 0, NULL, NULL);
*oport = scheme_multiple_array[1];
return scheme_multiple_array[0];
}
@ -4517,7 +4579,7 @@ Scheme_Object *scheme_open_output_file_with_mode(const char *name, const char *w
a[0]= scheme_make_path(name);
a[1] = truncate_replace_symbol;
a[2] = (text ? text_symbol : binary_symbol);
return scheme_do_open_output_file((char *)who, 0, 3, a, 0);
return scheme_do_open_output_file((char *)who, 0, 3, a, 0, 0, NULL, NULL);
}
Scheme_Object *

View File

@ -2436,7 +2436,7 @@ make_output_port (int argc, Scheme_Object *argv[])
static Scheme_Object *
open_input_file (int argc, Scheme_Object *argv[])
{
return scheme_do_open_input_file("open-input-file", 0, argc, argv, 0);
return scheme_do_open_input_file("open-input-file", 0, argc, argv, 0, NULL, NULL);
}
static Scheme_Object *
@ -2477,13 +2477,13 @@ open_input_char_string (int argc, Scheme_Object *argv[])
static Scheme_Object *
open_output_file (int argc, Scheme_Object *argv[])
{
return scheme_do_open_output_file("open-output-file", 0, argc, argv, 0);
return scheme_do_open_output_file("open-output-file", 0, argc, argv, 0, 0, NULL, NULL);
}
static Scheme_Object *
open_input_output_file (int argc, Scheme_Object *argv[])
{
return scheme_do_open_output_file("open-input-output-file", 0, argc, argv, 1);
return scheme_do_open_output_file("open-input-output-file", 0, argc, argv, 1, 0, NULL, NULL);
}
static Scheme_Object *
@ -2603,7 +2603,7 @@ call_with_output_file (int argc, Scheme_Object *argv[])
scheme_check_proc_arity("call-with-output-file", 1, 1, argc, argv);
port = scheme_do_open_output_file("call-with-output-file", 1, argc, argv, 0);
port = scheme_do_open_output_file("call-with-output-file", 1, argc, argv, 0, 0, NULL, NULL);
v = _scheme_apply_multi(argv[1], 1, &port);
@ -2628,7 +2628,7 @@ call_with_input_file(int argc, Scheme_Object *argv[])
scheme_check_proc_arity("call-with-input-file", 1, 1, argc, argv);
port = scheme_do_open_input_file("call-with-input-file", 1, argc, argv, 0);
port = scheme_do_open_input_file("call-with-input-file", 1, argc, argv, 0, NULL, NULL);
v = _scheme_apply_multi(argv[1], 1, &port);
@ -2665,7 +2665,7 @@ with_output_to_file (int argc, Scheme_Object *argv[])
scheme_check_proc_arity("with-output-to-file", 0, 1, argc, argv);
port = scheme_do_open_output_file("with-output-to-file", 1, argc, argv, 0);
port = scheme_do_open_output_file("with-output-to-file", 1, argc, argv, 0, 0, NULL, NULL);
config = scheme_extend_config(scheme_current_config(),
MZCONFIG_OUTPUT_PORT,
@ -2699,7 +2699,7 @@ with_input_from_file(int argc, Scheme_Object *argv[])
scheme_check_proc_arity("with-input-from-file", 0, 1, argc, argv);
port = scheme_do_open_input_file("with-input-from-file", 1, argc, argv, 0);
port = scheme_do_open_input_file("with-input-from-file", 1, argc, argv, 0, NULL, NULL);
config = scheme_extend_config(scheme_current_config(),
MZCONFIG_INPUT_PORT,
@ -4364,7 +4364,7 @@ static Scheme_Object *default_load(int argc, Scheme_Object *argv[])
if (!SCHEME_FALSEP(expected_module) && !SCHEME_SYMBOLP(expected_module))
scheme_wrong_type("default-load-handler", "symbol or #f", 1, argc, argv);
port = scheme_do_open_input_file("default-load-handler", 0, 1, argv, 0);
port = scheme_do_open_input_file("default-load-handler", 0, 1, argv, 0, NULL, NULL);
/* Turn on line/column counting, unless it's a .zo file: */
if (SCHEME_PATHP(argv[0])) {

View File

@ -5651,7 +5651,7 @@ Scheme_Object *scheme_load_delayed_code(int _which, Scheme_Load_Delay *_delay_in
scheme_release_file_descriptor();
a[0] = delay_info->path;
port = scheme_do_open_input_file("on-demand-loader", 0, 1, a, 0);
port = scheme_do_open_input_file("on-demand-loader", 0, 1, a, 0, NULL, NULL);
savebuf = scheme_current_thread->error_buf;
scheme_current_thread->error_buf = &newbuf;

View File

@ -3460,8 +3460,10 @@ THREAD_LOCAL_DECL(extern int scheme_force_port_closed);
void scheme_flush_orig_outputs(void);
Scheme_Object *scheme_file_stream_port_p(int, Scheme_Object *[]);
Scheme_Object *scheme_terminal_port_p(int, Scheme_Object *[]);
Scheme_Object *scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[], int internal);
Scheme_Object *scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv[], int and_read);
Scheme_Object *scheme_do_open_input_file(char *name, int offset, int argc, Scheme_Object *argv[],
int internal, char **err, int *eerrno);
Scheme_Object *scheme_do_open_output_file(char *name, int offset, int argc, Scheme_Object *argv[], int and_read,
int internal, char **err, int *eerrno);
Scheme_Object *scheme_file_position(int argc, Scheme_Object *argv[]);
Scheme_Object *scheme_file_buffer(int argc, Scheme_Object *argv[]);
Scheme_Object *scheme_file_identity(int argc, Scheme_Object *argv[]);