From 7f887fdf60facd57a4dea6e0e8c8bb03d14771f1 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Mon, 19 Sep 2011 08:11:17 -0600 Subject: [PATCH] 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. --- .../scribblings/reference/filesystem.scrbl | 18 ++- src/racket/src/file.c | 107 +++++++------ src/racket/src/port.c | 146 +++++++++++++----- src/racket/src/portfun.c | 16 +- src/racket/src/read.c | 2 +- src/racket/src/schpriv.h | 6 +- 6 files changed, 187 insertions(+), 108 deletions(-) diff --git a/collects/scribblings/reference/filesystem.scrbl b/collects/scribblings/reference/filesystem.scrbl index fd7289653d..ecf9e5572c 100644 --- a/collects/scribblings/reference/filesystem.scrbl +++ b/collects/scribblings/reference/filesystem.scrbl @@ -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?]{ diff --git a/src/racket/src/file.c b/src/racket/src/file.c index 1f960ee9ac..a1d6ae8c24 100644 --- a/src/racket/src/file.c +++ b/src/racket/src/file.c @@ -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"; diff --git a/src/racket/src/port.c b/src/racket/src/port.c index f00d81e8eb..d72119cadc 100644 --- a/src/racket/src/port.c +++ b/src/racket/src/port.c @@ -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 * diff --git a/src/racket/src/portfun.c b/src/racket/src/portfun.c index 30ac39653e..053a77c2e1 100644 --- a/src/racket/src/portfun.c +++ b/src/racket/src/portfun.c @@ -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])) { diff --git a/src/racket/src/read.c b/src/racket/src/read.c index 213c731fb8..f32120ec0a 100644 --- a/src/racket/src/read.c +++ b/src/racket/src/read.c @@ -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; diff --git a/src/racket/src/schpriv.h b/src/racket/src/schpriv.h index 93c43ab06c..9e0ef1871a 100644 --- a/src/racket/src/schpriv.h +++ b/src/racket/src/schpriv.h @@ -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[]);