X-Git-Url: https://perl5.git.perl.org/perl5.git/blobdiff_plain/8ab93df006894c4c166b3684129c3180b5febe48..3074a83070878eb5e9c440e75e1f005b50b45c68:/doio.c diff --git a/doio.c b/doio.c index 37ba0fa..4b8923f 100644 --- a/doio.c +++ b/doio.c @@ -60,6 +60,272 @@ #include +void +Perl_setfd_cloexec(int fd) +{ + assert(fd >= 0); +#if defined(HAS_FCNTL) && defined(F_SETFD) && defined(FD_CLOEXEC) + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif +} + +void +Perl_setfd_inhexec(int fd) +{ + assert(fd >= 0); +#if defined(HAS_FCNTL) && defined(F_SETFD) && defined(FD_CLOEXEC) + (void) fcntl(fd, F_SETFD, 0); +#endif +} + +void +Perl_setfd_cloexec_for_nonsysfd(pTHX_ int fd) +{ + assert(fd >= 0); + if(fd > PL_maxsysfd) + setfd_cloexec(fd); +} + +void +Perl_setfd_inhexec_for_sysfd(pTHX_ int fd) +{ + assert(fd >= 0); + if(fd <= PL_maxsysfd) + setfd_inhexec(fd); +} +void +Perl_setfd_cloexec_or_inhexec_by_sysfdness(pTHX_ int fd) +{ + assert(fd >= 0); + if(fd <= PL_maxsysfd) + setfd_inhexec(fd); + else + setfd_cloexec(fd); +} + + +#define DO_GENOPEN_THEN_CLOEXEC(GENOPEN_NORMAL, GENSETFD_CLOEXEC) \ + do { \ + int res = (GENOPEN_NORMAL); \ + if(LIKELY(res != -1)) GENSETFD_CLOEXEC; \ + return res; \ + } while(0) +#if defined(HAS_FCNTL) && defined(F_SETFD) && defined(FD_CLOEXEC) && \ + defined(F_GETFD) +enum { CLOEXEC_EXPERIMENT, CLOEXEC_AT_OPEN, CLOEXEC_AFTER_OPEN }; +# define DO_GENOPEN_EXPERIMENTING_CLOEXEC(TESTFD, GENOPEN_CLOEXEC, \ + GENOPEN_NORMAL, GENSETFD_CLOEXEC) \ + do { \ + static int strategy = CLOEXEC_EXPERIMENT; \ + switch (strategy) { \ + case CLOEXEC_EXPERIMENT: default: { \ + int res = (GENOPEN_CLOEXEC), eno; \ + if (LIKELY(res != -1)) { \ + int fdflags = fcntl((TESTFD), F_GETFD); \ + if (LIKELY(fdflags != -1) && \ + LIKELY(fdflags & FD_CLOEXEC)) { \ + strategy = CLOEXEC_AT_OPEN; \ + } else { \ + strategy = CLOEXEC_AFTER_OPEN; \ + GENSETFD_CLOEXEC; \ + } \ + } else if (UNLIKELY((eno = errno) == EINVAL || \ + eno == ENOSYS)) { \ + res = (GENOPEN_NORMAL); \ + if (LIKELY(res != -1)) { \ + strategy = CLOEXEC_AFTER_OPEN; \ + GENSETFD_CLOEXEC; \ + } else if (!LIKELY((eno = errno) == EINVAL || \ + eno == ENOSYS)) { \ + strategy = CLOEXEC_AFTER_OPEN; \ + } \ + } \ + return res; \ + } \ + case CLOEXEC_AT_OPEN: \ + return (GENOPEN_CLOEXEC); \ + case CLOEXEC_AFTER_OPEN: \ + DO_GENOPEN_THEN_CLOEXEC(GENOPEN_NORMAL, GENSETFD_CLOEXEC); \ + } \ + } while(0) +#else +# define DO_GENOPEN_EXPERIMENTING_CLOEXEC(TESTFD, GENOPEN_CLOEXEC, \ + GENOPEN_NORMAL, GENSETFD_CLOEXEC) \ + DO_GENOPEN_THEN_CLOEXEC(GENOPEN_NORMAL, GENSETFD_CLOEXEC) +#endif + +#define DO_ONEOPEN_THEN_CLOEXEC(ONEOPEN_NORMAL) \ + do { \ + int fd; \ + DO_GENOPEN_THEN_CLOEXEC(fd = (ONEOPEN_NORMAL), \ + setfd_cloexec(fd)); \ + } while(0) +#define DO_ONEOPEN_EXPERIMENTING_CLOEXEC(ONEOPEN_CLOEXEC, ONEOPEN_NORMAL) \ + do { \ + int fd; \ + DO_GENOPEN_EXPERIMENTING_CLOEXEC(fd, fd = (ONEOPEN_CLOEXEC), \ + fd = (ONEOPEN_NORMAL), setfd_cloexec(fd)); \ + } while(0) + +#define DO_PIPESETFD_CLOEXEC(PIPEFD) \ + do { \ + setfd_cloexec((PIPEFD)[0]); \ + setfd_cloexec((PIPEFD)[1]); \ + } while(0) +#define DO_PIPEOPEN_THEN_CLOEXEC(PIPEFD, PIPEOPEN_NORMAL) \ + DO_GENOPEN_THEN_CLOEXEC(PIPEOPEN_NORMAL, DO_PIPESETFD_CLOEXEC(PIPEFD)) +#define DO_PIPEOPEN_EXPERIMENTING_CLOEXEC(PIPEFD, PIPEOPEN_CLOEXEC, \ + PIPEOPEN_NORMAL) \ + DO_GENOPEN_EXPERIMENTING_CLOEXEC((PIPEFD)[0], PIPEOPEN_CLOEXEC, \ + PIPEOPEN_NORMAL, DO_PIPESETFD_CLOEXEC(PIPEFD)) + +int +Perl_PerlLIO_dup_cloexec(pTHX_ int oldfd) +{ +#if !defined(PERL_IMPLICIT_SYS) && defined(F_DUPFD_CLOEXEC) + /* + * struct IPerlLIO doesn't cover fcntl(), and there's no clear way + * to extend it, so for the time being this just isn't available on + * PERL_IMPLICIT_SYS builds. + */ + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + fcntl(oldfd, F_DUPFD_CLOEXEC, 0), + PerlLIO_dup(oldfd)); +#else + DO_ONEOPEN_THEN_CLOEXEC(PerlLIO_dup(oldfd)); +#endif +} + +int +Perl_PerlLIO_dup2_cloexec(pTHX_ int oldfd, int newfd) +{ +#if !defined(PERL_IMPLICIT_SYS) && defined(HAS_DUP3) && defined(O_CLOEXEC) + /* + * struct IPerlLIO doesn't cover dup3(), and there's no clear way + * to extend it, so for the time being this just isn't available on + * PERL_IMPLICIT_SYS builds. + */ + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + dup3(oldfd, newfd, O_CLOEXEC), + PerlLIO_dup2(oldfd, newfd)); +#else + DO_ONEOPEN_THEN_CLOEXEC(PerlLIO_dup2(oldfd, newfd)); +#endif +} + +int +Perl_PerlLIO_open_cloexec(pTHX_ const char *file, int flag) +{ + PERL_ARGS_ASSERT_PERLLIO_OPEN_CLOEXEC; +#if defined(O_CLOEXEC) + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + PerlLIO_open(file, flag | O_CLOEXEC), + PerlLIO_open(file, flag)); +#else + DO_ONEOPEN_THEN_CLOEXEC(PerlLIO_open(file, flag)); +#endif +} + +int +Perl_PerlLIO_open3_cloexec(pTHX_ const char *file, int flag, int perm) +{ + PERL_ARGS_ASSERT_PERLLIO_OPEN3_CLOEXEC; +#if defined(O_CLOEXEC) + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + PerlLIO_open3(file, flag | O_CLOEXEC, perm), + PerlLIO_open3(file, flag, perm)); +#else + DO_ONEOPEN_THEN_CLOEXEC(PerlLIO_open3(file, flag, perm)); +#endif +} + +int +Perl_my_mkstemp_cloexec(char *templte) +{ + PERL_ARGS_ASSERT_MY_MKSTEMP_CLOEXEC; +#if defined(O_CLOEXEC) + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + Perl_my_mkostemp(templte, O_CLOEXEC), + Perl_my_mkstemp(templte)); +#else + DO_ONEOPEN_THEN_CLOEXEC(Perl_my_mkstemp(templte)); +#endif +} + +#ifdef HAS_PIPE +int +Perl_PerlProc_pipe_cloexec(pTHX_ int *pipefd) +{ + PERL_ARGS_ASSERT_PERLPROC_PIPE_CLOEXEC; + /* + * struct IPerlProc doesn't cover pipe2(), and there's no clear way + * to extend it, so for the time being this just isn't available on + * PERL_IMPLICIT_SYS builds. + */ +# if !defined(PERL_IMPLICIT_SYS) && defined(HAS_PIPE2) && defined(O_CLOEXEC) + DO_PIPEOPEN_EXPERIMENTING_CLOEXEC(pipefd, + pipe2(pipefd, O_CLOEXEC), + PerlProc_pipe(pipefd)); +# else + DO_PIPEOPEN_THEN_CLOEXEC(pipefd, PerlProc_pipe(pipefd)); +# endif +} +#endif + +#ifdef HAS_SOCKET + +int +Perl_PerlSock_socket_cloexec(pTHX_ int domain, int type, int protocol) +{ +# if defined(SOCK_CLOEXEC) + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + PerlSock_socket(domain, type | SOCK_CLOEXEC, protocol), + PerlSock_socket(domain, type, protocol)); +# else + DO_ONEOPEN_THEN_CLOEXEC(PerlSock_socket(domain, type, protocol)); +# endif +} + +int +Perl_PerlSock_accept_cloexec(pTHX_ int listenfd, struct sockaddr *addr, + Sock_size_t *addrlen) +{ +# if !defined(PERL_IMPLICIT_SYS) && \ + defined(HAS_ACCEPT4) && defined(SOCK_CLOEXEC) + /* + * struct IPerlSock doesn't cover accept4(), and there's no clear + * way to extend it, so for the time being this just isn't available + * on PERL_IMPLICIT_SYS builds. + */ + DO_ONEOPEN_EXPERIMENTING_CLOEXEC( + accept4(listenfd, addr, addrlen, SOCK_CLOEXEC), + PerlSock_accept(listenfd, addr, addrlen)); +# else + DO_ONEOPEN_THEN_CLOEXEC(PerlSock_accept(listenfd, addr, addrlen)); +# endif +} + +#endif + +#if defined (HAS_SOCKETPAIR) || \ + (defined (HAS_SOCKET) && defined(SOCK_DGRAM) && \ + defined(AF_INET) && defined(PF_INET)) +int +Perl_PerlSock_socketpair_cloexec(pTHX_ int domain, int type, int protocol, + int *pairfd) +{ + PERL_ARGS_ASSERT_PERLSOCK_SOCKETPAIR_CLOEXEC; +# ifdef SOCK_CLOEXEC + DO_PIPEOPEN_EXPERIMENTING_CLOEXEC(pairfd, + PerlSock_socketpair(domain, type | SOCK_CLOEXEC, protocol, pairfd), + PerlSock_socketpair(domain, type, protocol, pairfd)); +# else + DO_PIPEOPEN_THEN_CLOEXEC(pairfd, + PerlSock_socketpair(domain, type, protocol, pairfd)); +# endif +} +#endif + static IO * S_openn_setup(pTHX_ GV *gv, char *mode, PerlIO **saveifp, PerlIO **saveofp, int *savefd, char *savetype) @@ -261,7 +527,7 @@ Perl_do_open6(pTHX_ GV *gv, const char *oname, STRLEN len, STRLEN nlen = 0; /* New style explicit name, type is just mode and layer info */ #ifdef USE_STDIO - if (SvROK(*svp) && !strchr(oname,'&')) { + if (SvROK(*svp) && !memchr(oname, '&', len)) { if (ckWARN(WARN_IO)) Perl_warner(aTHX_ packWARN(WARN_IO), "Can't open a reference"); @@ -452,7 +718,7 @@ Perl_do_open6(pTHX_ GV *gv, const char *oname, STRLEN len, } else { if (dodup) - wanted_fd = PerlLIO_dup(wanted_fd); + wanted_fd = PerlLIO_dup_cloexec(wanted_fd); else was_fdopen = TRUE; if (!(fp = PerlIO_openn(aTHX_ type,mode,wanted_fd,0,0,NULL,num_svs,svp))) { @@ -621,15 +887,17 @@ S_openn_cleanup(pTHX_ GV *gv, IO *io, PerlIO *fp, char *mode, const char *oname, PERL_ARGS_ASSERT_OPENN_CLEANUP; + Zero(&statbuf, 1, Stat_t); + if (!fp) { if (IoTYPE(io) == IoTYPE_RDONLY && ckWARN(WARN_NEWLINE) && should_warn_nl(oname) ) { - GCC_DIAG_IGNORE(-Wformat-nonliteral); /* PL_warn_nl is constant */ + GCC_DIAG_IGNORE_STMT(-Wformat-nonliteral); /* PL_warn_nl is constant */ Perl_warner(aTHX_ packWARN(WARN_NEWLINE), PL_warn_nl, "open"); - GCC_DIAG_RESTORE; + GCC_DIAG_RESTORE_STMT; } goto say_false; } @@ -741,33 +1009,15 @@ S_openn_cleanup(pTHX_ GV *gv, IO *io, PerlIO *fp, char *mode, const char *oname, if (was_fdopen) { /* need to close fp without closing underlying fd */ int ofd = PerlIO_fileno(fp); - int dupfd = ofd >= 0 ? PerlLIO_dup(ofd) : -1; -#if defined(HAS_FCNTL) && defined(F_SETFD) - /* Assume if we have F_SETFD we have F_GETFD. */ - /* Get a copy of all the fd flags. */ - int fd_flags = ofd >= 0 ? fcntl(ofd, F_GETFD) : -1; - if (fd_flags < 0) { - if (dupfd >= 0) - PerlLIO_close(dupfd); - goto say_false; - } -#endif + int dupfd = ofd >= 0 ? PerlLIO_dup_cloexec(ofd) : -1; if (ofd < 0 || dupfd < 0) { if (dupfd >= 0) PerlLIO_close(dupfd); goto say_false; } PerlIO_close(fp); - PerlLIO_dup2(dupfd, ofd); -#if defined(HAS_FCNTL) && defined(F_SETFD) - /* The dup trick has lost close-on-exec on ofd, - * and possibly any other flags, so restore them. */ - if (fcntl(ofd,F_SETFD, fd_flags) < 0) { - if (dupfd >= 0) - PerlLIO_close(dupfd); - goto say_false; - } -#endif + PerlLIO_dup2_cloexec(dupfd, ofd); + setfd_inhexec_for_sysfd(ofd); PerlLIO_close(dupfd); } else @@ -777,12 +1027,6 @@ S_openn_cleanup(pTHX_ GV *gv, IO *io, PerlIO *fp, char *mode, const char *oname, PerlIO_clearerr(fp); fd = PerlIO_fileno(fp); } -#if defined(HAS_FCNTL) && defined(F_SETFD) && defined(FD_CLOEXEC) - if (fd >= 0 && fd > PL_maxsysfd && fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { - PerlLIO_close(fd); - goto say_false; - } -#endif IoIFP(io) = fp; IoFLAGS(io) &= ~IOf_NOLINE; @@ -850,7 +1094,11 @@ S_openindirtemp(pTHX_ GV *gv, SV *orig_name, SV *temp_out_name) { else sv_setpvs(temp_out_name, "XXXXXXXX"); - fd = Perl_my_mkstemp(SvPVX(temp_out_name)); + { + int old_umask = umask(0177); + fd = Perl_my_mkstemp_cloexec(SvPVX(temp_out_name)); + umask(old_umask); + } if (fd < 0) return FALSE; @@ -863,20 +1111,50 @@ S_openindirtemp(pTHX_ GV *gv, SV *orig_name, SV *temp_out_name) { } #if defined(HAS_UNLINKAT) && defined(HAS_RENAMEAT) && defined(HAS_FCHMODAT) && \ - (defined(HAS_DIRFD) || defined(HAS_DIR_DD_FD)) && !defined(NO_USE_ATFUNCTIONS) + (defined(HAS_DIRFD) || defined(HAS_DIR_DD_FD)) && !defined(NO_USE_ATFUNCTIONS) && \ + defined(HAS_LINKAT) # define ARGV_USE_ATFUNCTIONS #endif +/* Win32 doesn't necessarily return useful information + * in st_dev, st_ino. + */ +#ifndef DOSISH +# define ARGV_USE_STAT_INO +#endif + #define ARGVMG_BACKUP_NAME 0 #define ARGVMG_TEMP_NAME 1 #define ARGVMG_ORIG_NAME 2 #define ARGVMG_ORIG_MODE 3 -#define ARGVMG_ORIG_DIRP 4 +#define ARGVMG_ORIG_PID 4 + +/* we store the entire stat_t since the ino_t and dev_t values might + not fit in an IV. I could have created a new structure and + transferred them across, but this seemed too much effort for very + little win. + + We store it even when the *at() functions are available, since + while the C runtime might have definitions for these functions, the + operating system or a specific filesystem might not implement them. + eg. NetBSD 6 implements linkat() but only where the fds are AT_FDCWD. + */ +#ifdef ARGV_USE_STAT_INO +# define ARGVMG_ORIG_CWD_STAT 5 +#endif + +#ifdef ARGV_USE_ATFUNCTIONS +# define ARGVMG_ORIG_DIRP 6 +#endif + +#ifdef ENOTSUP +#define NotSupported(e) ((e) == ENOSYS || (e) == ENOTSUP) +#else +#define NotSupported(e) ((e) == ENOSYS) +#endif static int S_argvout_free(pTHX_ SV *io, MAGIC *mg) { - SV **temp_psv; - PERL_UNUSED_ARG(io); /* note this can be entered once the file has been @@ -891,23 +1169,38 @@ S_argvout_free(pTHX_ SV *io, MAGIC *mg) { SV **dir_psv; DIR *dir; #endif - /* if we get here the file hasn't been closed explicitly by the - user and hadn't been closed implicitly by nextargv(), so - abandon the edit */ PerlIO *iop = IoIFP(io); - (void)PerlIO_close(iop); - IoIFP(io) = IoOFP(io) = NULL; - temp_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_TEMP_NAME, FALSE); - assert(temp_psv && *temp_psv && SvPOK(*temp_psv)); + + assert(SvTYPE(mg->mg_obj) == SVt_PVAV); + + pid_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_PID, FALSE); + + assert(pid_psv && *pid_psv); + + if (SvIV(*pid_psv) == (IV)PerlProc_getpid()) { + /* if we get here the file hasn't been closed explicitly by the + user and hadn't been closed implicitly by nextargv(), so + abandon the edit */ + SV **temp_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_TEMP_NAME, FALSE); + const char *temp_pv = SvPVX(*temp_psv); + + assert(temp_psv && *temp_psv && SvPOK(*temp_psv)); + (void)PerlIO_close(iop); + IoIFP(io) = IoOFP(io) = NULL; #ifdef ARGV_USE_ATFUNCTIONS - dir_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_DIRP, FALSE); - assert(dir_psv && *dir_psv && SvIOK(*dir_psv)); - dir = INT2PTR(DIR *, SvIV(*dir_psv)); - (void)unlinkat(my_dirfd(dir), SvPVX(*temp_psv), 0); - closedir(dir); + dir_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_DIRP, FALSE); + assert(dir_psv && *dir_psv && SvIOK(*dir_psv)); + dir = INT2PTR(DIR *, SvIV(*dir_psv)); + if (dir) { + if (unlinkat(my_dirfd(dir), temp_pv, 0) < 0 && + NotSupported(errno)) + (void)UNLINK(temp_pv); + closedir(dir); + } #else - (void)UNLINK(SvPVX(*temp_psv)); + (void)UNLINK(temp_pv); #endif + } } return 0; @@ -929,9 +1222,14 @@ S_argvout_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) { 1: name of the temp output file 2: name of the original file 3: file mode of the original file + 4: pid of the process we opened at, to prevent doing the renaming + etc in both the child and the parent after a fork + +If we have useful inode/device ids in stat_t we also keep: + 5: a stat of the original current working directory If we have unlinkat(), renameat(), fchmodat(), dirfd() we also keep: - 4: the DIR * for the current directory when we open the file, stored as an IV + 6: the DIR * for the current directory when we open the file, stored as an IV */ static const MGVTBL argvout_vtbl = @@ -1089,11 +1387,18 @@ Perl_nextargv(pTHX_ GV *gv, bool nomagicopen) av_store(magic_av, ARGVMG_TEMP_NAME, temp_name_sv); av_store(magic_av, ARGVMG_ORIG_NAME, newSVsv(sv)); av_store(magic_av, ARGVMG_ORIG_MODE, newSVuv(PL_filemode)); -#ifdef ARGV_USE_ATFUNCTIONS + av_store(magic_av, ARGVMG_ORIG_PID, newSViv((IV)PerlProc_getpid())); +#if defined(ARGV_USE_ATFUNCTIONS) curdir = opendir("."); av_store(magic_av, ARGVMG_ORIG_DIRP, newSViv(PTR2IV(curdir))); +#elif defined(ARGV_USE_STAT_INO) + if (PerlLIO_stat(".", &statbuf) >= 0) { + av_store(magic_av, ARGVMG_ORIG_CWD_STAT, + newSVpvn((char *)&statbuf, sizeof(statbuf))); + } #endif setdefout(PL_argvoutgv); + sv_setsv(GvSVn(PL_argvoutgv), temp_name_sv); mg = sv_magicext((SV*)GvIOp(PL_argvoutgv), (SV*)magic_av, PERL_MAGIC_uvar, &argvout_vtbl, NULL, 0); mg->mg_flags |= MGf_DUP; SvREFCNT_dec(magic_av); @@ -1109,11 +1414,9 @@ Perl_nextargv(pTHX_ GV *gv, bool nomagicopen) /* XXX silently ignore failures */ #ifdef HAS_FCHOWN PERL_UNUSED_RESULT(fchown(PL_lastfd,fileuid,filegid)); -#else -#ifdef HAS_CHOWN +#elif defined(HAS_CHOWN) PERL_UNUSED_RESULT(PerlLIO_chown(PL_oldname,fileuid,filegid)); #endif -#endif } } return IoIFP(GvIOp(gv)); @@ -1151,6 +1454,72 @@ Perl_nextargv(pTHX_ GV *gv, bool nomagicopen) return NULL; } +#ifdef ARGV_USE_ATFUNCTIONS +# if defined(__FreeBSD__) + +/* FreeBSD 11 renameat() mis-behaves strangely with absolute paths in cases where the + * equivalent rename() succeeds + */ +static int +S_my_renameat(int olddfd, const char *oldpath, int newdfd, const char *newpath) { + /* this is intended only for use in Perl_do_close() */ + assert(olddfd == newdfd); + assert(PERL_FILE_IS_ABSOLUTE(oldpath) == PERL_FILE_IS_ABSOLUTE(newpath)); + if (PERL_FILE_IS_ABSOLUTE(oldpath)) { + return PerlLIO_rename(oldpath, newpath); + } + else { + return renameat(olddfd, oldpath, newdfd, newpath); + } +} + +# else +# define S_my_renameat(dh1, pv1, dh2, pv2) renameat((dh1), (pv1), (dh2), (pv2)) +# endif /* if defined(__FreeBSD__) */ +#endif + +static bool +S_dir_unchanged(pTHX_ const char *orig_pv, MAGIC *mg) { + Stat_t statbuf; + +#ifdef ARGV_USE_STAT_INO + SV **stat_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_CWD_STAT, FALSE); + Stat_t *orig_cwd_stat = stat_psv && *stat_psv ? (Stat_t *)SvPVX(*stat_psv) : NULL; + + /* if the path is absolute the possible moving of cwd (which the file + might be in) isn't our problem. + This code tries to be reasonably balanced about detecting a changed + CWD, if we have the information needed to check that curdir has changed, we + check it + */ + if (!PERL_FILE_IS_ABSOLUTE(orig_pv) + && orig_cwd_stat + && PerlLIO_stat(".", &statbuf) >= 0 + && ( statbuf.st_dev != orig_cwd_stat->st_dev + || statbuf.st_ino != orig_cwd_stat->st_ino)) { + Perl_croak(aTHX_ "Cannot complete in-place edit of %s: %s", + orig_pv, "Current directory has changed"); + } +#else + SV **temp_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_TEMP_NAME, FALSE); + + /* Some platforms don't have useful st_ino etc, so just + check we can see the work file. + */ + if (!PERL_FILE_IS_ABSOLUTE(orig_pv) + && PerlLIO_stat(SvPVX(*temp_psv), &statbuf) < 0) { + Perl_croak(aTHX_ "Cannot complete in-place edit of %s: %s", + orig_pv, + "Work file is missing - did you change directory?"); + } +#endif + + return TRUE; +} + +#define dir_unchanged(orig_psv, mg) \ + S_dir_unchanged(aTHX_ (orig_psv), (mg)) + /* explicit renamed to avoid C++ conflict -- kja */ bool Perl_do_close(pTHX_ GV *gv, bool not_implicit) @@ -1182,8 +1551,9 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) /* PL_oldname may have been modified by a nested ARGV use at this point */ SV **orig_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_NAME, FALSE); SV **mode_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_MODE, FALSE); -#ifdef ARGV_USE_ATFUNCTIONS - SV **dir_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_DIRP, FALSE); + SV **pid_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_PID, FALSE); +#if defined(ARGV_USE_ATFUNCTIONS) + SV **dir_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_ORIG_DIRP, FALSE); DIR *dir; int dfd; #endif @@ -1195,6 +1565,7 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) assert(temp_psv && *temp_psv); assert(orig_psv && *orig_psv); assert(mode_psv && *mode_psv); + assert(pid_psv && *pid_psv); #ifdef ARGV_USE_ATFUNCTIONS assert(dir_psv && *dir_psv); dir = INT2PTR(DIR *, SvIVX(*dir_psv)); @@ -1202,7 +1573,6 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) #endif orig_pv = SvPVX(*orig_psv); - mode = SvUV(*mode_psv); if ((mode & (S_ISUID|S_ISGID)) != 0 @@ -1217,6 +1587,12 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) retval = io_close(io, NULL, not_implicit, FALSE); + if (SvIV(*pid_psv) != (IV)PerlProc_getpid()) { + /* this is a child process, don't duplicate our rename() etc + processing below */ + goto freext; + } + if (retval) { #if defined(DOSISH) || defined(__CYGWIN__) if (PL_argvgv && GvIOp(PL_argvgv) @@ -1225,11 +1601,18 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) do_close(PL_argvgv, FALSE); } #endif +#ifndef ARGV_USE_ATFUNCTIONS + if (!dir_unchanged(orig_pv, mg)) + goto abort_inplace; +#endif if (back_psv && *back_psv) { #if defined(HAS_LINK) && !defined(DOSISH) && !defined(__CYGWIN__) && defined(HAS_RENAME) if ( # ifdef ARGV_USE_ATFUNCTIONS - linkat(dfd, orig_pv, dfd, SvPVX(*back_psv), 0) < 0 + linkat(dfd, orig_pv, dfd, SvPVX(*back_psv), 0) < 0 && + !(UNLIKELY(NotSupported(errno)) && + dir_unchanged(orig_pv, mg) && + link(orig_pv, SvPVX(*back_psv)) == 0) # else link(orig_pv, SvPVX(*back_psv)) < 0 # endif @@ -1239,12 +1622,23 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) #ifdef HAS_RENAME if ( # ifdef ARGV_USE_ATFUNCTIONS - renameat(dfd, orig_pv, dfd, SvPVX(*back_psv)) < 0 + S_my_renameat(dfd, orig_pv, dfd, SvPVX(*back_psv)) < 0 && + !(UNLIKELY(NotSupported(errno)) && + dir_unchanged(orig_pv, mg) && + PerlLIO_rename(orig_pv, SvPVX(*back_psv)) == 0) # else PerlLIO_rename(orig_pv, SvPVX(*back_psv)) < 0 # endif ) { if (!not_implicit) { +# ifdef ARGV_USE_ATFUNCTIONS + if (unlinkat(dfd, SvPVX_const(*temp_psv), 0) < 0 && + UNLIKELY(NotSupported(errno)) && + dir_unchanged(orig_pv, mg)) + (void)UNLINK(SvPVX_const(*temp_psv)); +# else + UNLINK(SvPVX(*temp_psv)); +# endif Perl_croak(aTHX_ "Can't rename %s to %s: %s, skipping file", SvPVX(*orig_psv), SvPVX(*back_psv), Strerror(errno)); } @@ -1272,19 +1666,28 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) } #endif if ( -#ifdef HAS_RENAME -# ifdef ARGV_USE_ATFUNCTIONS - renameat(dfd, SvPVX(*temp_psv), dfd, orig_pv) < 0 -# else - PerlLIO_rename(SvPVX(*temp_psv), orig_pv) < 0 -# endif -#else +#if !defined(HAS_RENAME) link(SvPVX(*temp_psv), orig_pv) < 0 +#elif defined(ARGV_USE_ATFUNCTIONS) + S_my_renameat(dfd, SvPVX(*temp_psv), dfd, orig_pv) < 0 && + !(UNLIKELY(NotSupported(errno)) && + dir_unchanged(orig_pv, mg) && + PerlLIO_rename(SvPVX(*temp_psv), orig_pv) == 0) +#else + PerlLIO_rename(SvPVX(*temp_psv), orig_pv) < 0 #endif ) { if (!not_implicit) { - Perl_croak(aTHX_ "Can't rename in-place work file '%s' to '%s': %s\n", - SvPVX(*temp_psv), SvPVX(*orig_psv), Strerror(errno)); +#ifdef ARGV_USE_ATFUNCTIONS + if (unlinkat(dfd, SvPVX_const(*temp_psv), 0) < 0 && + NotSupported(errno)) + UNLINK(SvPVX(*temp_psv)); +#else + UNLINK(SvPVX(*temp_psv)); +#endif + /* diag_listed_as: Cannot complete in-place edit of %s: %s */ + Perl_croak(aTHX_ "Cannot complete in-place edit of %s: failed to rename work file '%s' to '%s': %s", + orig_pv, SvPVX(*temp_psv), orig_pv, Strerror(errno)); } abort_inplace: UNLINK(SvPVX_const(*temp_psv)); @@ -1296,7 +1699,10 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) } else { #ifdef ARGV_USE_ATFUNCTIONS - unlinkat(dfd, SvPVX_const(*temp_psv), 0); + if (unlinkat(dfd, SvPVX_const(*temp_psv), 0) && + NotSupported(errno)) + UNLINK(SvPVX_const(*temp_psv)); + #else UNLINK(SvPVX_const(*temp_psv)); #endif @@ -1305,6 +1711,7 @@ Perl_do_close(pTHX_ GV *gv, bool not_implicit) SvPVX(*temp_psv), Strerror(errno)); } } + freext: mg_freeext((SV*)io, PERL_MAGIC_uvar, &argvout_vtbl); } else { @@ -1517,7 +1924,7 @@ Perl_mode_from_discipline(pTHX_ const char *s, STRLEN len) else { const char *end; fail_discipline: - end = strchr(s+1, ':'); + end = (char *) memchr(s+1, ':', len); if (!end) end = s+len; #ifndef PERLIO_LAYERS @@ -1666,8 +2073,11 @@ Perl_my_stat_flags(pTHX_ const U32 flags) if (PL_op->op_flags & OPf_REF) { gv = cGVOP_gv; do_fstat: - if (gv == PL_defgv) + if (gv == PL_defgv) { + if (PL_laststatval < 0) + SETERRNO(EBADF,RMS_IFI); return PL_laststatval; + } io = GvIO(gv); do_fstat_have_io: PL_laststype = OP_STAT; @@ -1678,6 +2088,7 @@ Perl_my_stat_flags(pTHX_ const U32 flags) int fd = PerlIO_fileno(IoIFP(io)); if (fd < 0) { /* E.g. PerlIO::scalar has no real fd. */ + SETERRNO(EBADF,RMS_IFI); return (PL_laststatval = -1); } else { return (PL_laststatval = PerlLIO_fstat(fd, &PL_statcache)); @@ -1688,6 +2099,7 @@ Perl_my_stat_flags(pTHX_ const U32 flags) } PL_laststatval = -1; report_evil_fh(gv); + SETERRNO(EBADF,RMS_IFI); return -1; } else if ((PL_op->op_private & (OPpFT_STACKED|OPpFT_AFTER_t)) @@ -1695,7 +2107,7 @@ Perl_my_stat_flags(pTHX_ const U32 flags) return PL_laststatval; else { SV* const sv = TOPs; - const char *s; + const char *s, *d; STRLEN len; if ((gv = MAYBE_DEREF_GV_flags(sv,flags))) { goto do_fstat; @@ -1709,13 +2121,18 @@ Perl_my_stat_flags(pTHX_ const U32 flags) s = SvPV_flags_const(sv, len, flags); PL_statgv = NULL; sv_setpvn(PL_statname, s, len); - s = SvPVX_const(PL_statname); /* s now NUL-terminated */ + d = SvPVX_const(PL_statname); /* s now NUL-terminated */ PL_laststype = OP_STAT; - PL_laststatval = PerlLIO_stat(s, &PL_statcache); + if (!IS_SAFE_PATHNAME(s, len, OP_NAME(PL_op))) { + PL_laststatval = -1; + } + else { + PL_laststatval = PerlLIO_stat(d, &PL_statcache); + } if (PL_laststatval < 0 && ckWARN(WARN_NEWLINE) && should_warn_nl(s)) { - GCC_DIAG_IGNORE(-Wformat-nonliteral); /* PL_warn_nl is constant */ + GCC_DIAG_IGNORE_STMT(-Wformat-nonliteral); /* PL_warn_nl is constant */ Perl_warner(aTHX_ packWARN(WARN_NEWLINE), PL_warn_nl, "stat"); - GCC_DIAG_RESTORE; + GCC_DIAG_RESTORE_STMT; } return PL_laststatval; } @@ -1728,12 +2145,15 @@ Perl_my_lstat_flags(pTHX_ const U32 flags) static const char* const no_prev_lstat = "The stat preceding -l _ wasn't an lstat"; dSP; const char *file; + STRLEN len; SV* const sv = TOPs; bool isio = FALSE; if (PL_op->op_flags & OPf_REF) { if (cGVOP_gv == PL_defgv) { if (PL_laststype != OP_LSTAT) Perl_croak(aTHX_ "%s", no_prev_lstat); + if (PL_laststatval < 0) + SETERRNO(EBADF,RMS_IFI); return PL_laststatval; } PL_laststatval = -1; @@ -1743,6 +2163,7 @@ Perl_my_lstat_flags(pTHX_ const U32 flags) "Use of -l on filehandle %" HEKf, HEKfARG(GvENAME_HEK(cGVOP_gv))); } + SETERRNO(EBADF,RMS_IFI); return -1; } if ((PL_op->op_private & (OPpFT_STACKED|OPpFT_AFTER_t)) @@ -1771,13 +2192,18 @@ Perl_my_lstat_flags(pTHX_ const U32 flags) HEKfARG(GvENAME_HEK((const GV *) (SvROK(sv) ? SvRV(sv) : sv)))); } - file = SvPV_flags_const_nolen(sv, flags); + file = SvPV_flags_const(sv, len, flags); sv_setpv(PL_statname,file); - PL_laststatval = PerlLIO_lstat(file,&PL_statcache); + if (!IS_SAFE_PATHNAME(file, len, OP_NAME(PL_op))) { + PL_laststatval = -1; + } + else { + PL_laststatval = PerlLIO_lstat(file,&PL_statcache); + } if (PL_laststatval < 0 && ckWARN(WARN_NEWLINE) && should_warn_nl(file)) { - GCC_DIAG_IGNORE(-Wformat-nonliteral); /* PL_warn_nl is constant */ + GCC_DIAG_IGNORE_STMT(-Wformat-nonliteral); /* PL_warn_nl is constant */ Perl_warner(aTHX_ packWARN(WARN_NEWLINE), PL_warn_nl, "lstat"); - GCC_DIAG_RESTORE; + GCC_DIAG_RESTORE_STMT; } return PL_laststatval; } @@ -1807,54 +2233,54 @@ Perl_do_aexec5(pTHX_ SV *really, SV **mark, SV **sp, #if defined(__SYMBIAN32__) || defined(__LIBCATAMOUNT__) Perl_croak(aTHX_ "exec? I'm not *that* kind of operating system"); #else - if (sp > mark) { - const char **a; + assert(sp >= mark); + ENTER; + { + const char **argv, **a; const char *tmps = NULL; - Newx(PL_Argv, sp - mark + 1, const char*); - a = PL_Argv; + Newx(argv, sp - mark + 1, const char*); + SAVEFREEPV(argv); + a = argv; while (++mark <= sp) { - if (*mark) - *a++ = SvPV_nolen_const(*mark); - else + if (*mark) { + char *arg = savepv(SvPV_nolen_const(*mark)); + SAVEFREEPV(arg); + *a++ = arg; + } else *a++ = ""; } *a = NULL; - if (really) - tmps = SvPV_nolen_const(really); - if ((!really && *PL_Argv[0] != '/') || + if (really) { + tmps = savepv(SvPV_nolen_const(really)); + SAVEFREEPV(tmps); + } + if ((!really && argv[0] && *argv[0] != '/') || (really && *tmps != '/')) /* will execvp use PATH? */ TAINT_ENV(); /* testing IFS here is overkill, probably */ PERL_FPU_PRE_EXEC if (really && *tmps) { - PerlProc_execvp(tmps,EXEC_ARGV_CAST(PL_Argv)); - } else { - PerlProc_execvp(PL_Argv[0],EXEC_ARGV_CAST(PL_Argv)); - } + PerlProc_execvp(tmps,EXEC_ARGV_CAST(argv)); + } else if (argv[0]) { + PerlProc_execvp(argv[0],EXEC_ARGV_CAST(argv)); + } else { + SETERRNO(ENOENT,RMS_FNF); + } PERL_FPU_POST_EXEC - S_exec_failed(aTHX_ (really ? tmps : PL_Argv[0]), fd, do_report); + S_exec_failed(aTHX_ (really ? tmps : argv[0] ? argv[0] : ""), fd, do_report); } - do_execfree(); + LEAVE; #endif return FALSE; } -void -Perl_do_execfree(pTHX) -{ - Safefree(PL_Argv); - PL_Argv = NULL; - Safefree(PL_Cmd); - PL_Cmd = NULL; -} - #ifdef PERL_DEFAULT_DO_EXEC3_IMPLEMENTATION bool Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) { dVAR; - const char **a; + const char **argv, **a; char *s; char *buf; char *cmd; @@ -1863,7 +2289,9 @@ Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) PERL_ARGS_ASSERT_DO_EXEC3; + ENTER; Newx(buf, cmdlen, char); + SAVEFREEPV(buf); cmd = buf; memcpy(cmd, incmd, cmdlen); @@ -1876,7 +2304,7 @@ Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) { char flags[PERL_FLAGS_MAX]; if (strnEQ(cmd,PL_cshname,PL_cshlen) && - strEQs(cmd+PL_cshlen," -c")) { + strBEGINs(cmd+PL_cshlen," -c")) { my_strlcpy(flags, "-c", PERL_FLAGS_MAX); s = cmd+PL_cshlen+3; if (*s == 'f') { @@ -1899,8 +2327,7 @@ Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) PERL_FPU_POST_EXEC *s = '\''; S_exec_failed(aTHX_ PL_cshname, fd, do_report); - Safefree(buf); - return FALSE; + goto leave; } } } @@ -1912,7 +2339,7 @@ Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) if (*cmd == '.' && isSPACE(cmd[1])) goto doshell; - if (strEQs(cmd,"exec") && isSPACE(cmd[4])) + if (strBEGINs(cmd,"exec") && isSPACE(cmd[4])) goto doshell; s = cmd; @@ -1947,15 +2374,16 @@ Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) PerlProc_execl(PL_sh_path, "sh", "-c", cmd, (char *)NULL); PERL_FPU_POST_EXEC S_exec_failed(aTHX_ PL_sh_path, fd, do_report); - Safefree(buf); - return FALSE; + goto leave; } } - Newx(PL_Argv, (s - cmd) / 2 + 2, const char*); - PL_Cmd = savepvn(cmd, s-cmd); - a = PL_Argv; - for (s = PL_Cmd; *s;) { + Newx(argv, (s - cmd) / 2 + 2, const char*); + SAVEFREEPV(argv); + cmd = savepvn(cmd, s-cmd); + SAVEFREEPV(cmd); + a = argv; + for (s = cmd; *s;) { while (isSPACE(*s)) s++; if (*s) @@ -1966,18 +2394,16 @@ Perl_do_exec3(pTHX_ const char *incmd, int fd, int do_report) *s++ = '\0'; } *a = NULL; - if (PL_Argv[0]) { + if (argv[0]) { PERL_FPU_PRE_EXEC - PerlProc_execvp(PL_Argv[0],EXEC_ARGV_CAST(PL_Argv)); + PerlProc_execvp(argv[0],EXEC_ARGV_CAST(argv)); PERL_FPU_POST_EXEC - if (errno == ENOEXEC) { /* for system V NIH syndrome */ - do_execfree(); + if (errno == ENOEXEC) /* for system V NIH syndrome */ goto doshell; - } - S_exec_failed(aTHX_ PL_Argv[0], fd, do_report); + S_exec_failed(aTHX_ argv[0], fd, do_report); } - do_execfree(); - Safefree(buf); +leave: + LEAVE; return FALSE; } @@ -2808,33 +3234,29 @@ Perl_vms_start_glob fp = Perl_vms_start_glob(aTHX_ tmpglob, io); #else /* !VMS */ -#ifdef DOSISH -#ifdef OS2 +# ifdef DOSISH +# if defined(OS2) sv_setpv(tmpcmd, "for a in "); sv_catsv(tmpcmd, tmpglob); sv_catpv(tmpcmd, "; do echo \"$a\\0\\c\"; done |"); -#else -#ifdef DJGPP +# elif defined(DJGPP) sv_setpv(tmpcmd, "/dev/dosglob/"); /* File System Extension */ sv_catsv(tmpcmd, tmpglob); -#else +# else sv_setpv(tmpcmd, "perlglob "); sv_catsv(tmpcmd, tmpglob); sv_catpv(tmpcmd, " |"); -#endif /* !DJGPP */ -#endif /* !OS2 */ -#else /* !DOSISH */ -#if defined(CSH) +# endif +# elif defined(CSH) sv_setpvn(tmpcmd, PL_cshname, PL_cshlen); sv_catpv(tmpcmd, " -cf 'set nonomatch; glob "); sv_catsv(tmpcmd, tmpglob); sv_catpv(tmpcmd, "' 2>/dev/null |"); -#else +# else sv_setpv(tmpcmd, "echo "); sv_catsv(tmpcmd, tmpglob); sv_catpv(tmpcmd, "|tr -s ' \t\f\r' '\\n\\n\\n\\n'|"); -#endif /* !CSH */ -#endif /* !DOSISH */ +# endif /* !DOSISH && !CSH */ { SV ** const svp = hv_fetchs(GvHVn(PL_envgv), "LS_COLORS", 0); if (svp && *svp)