+#ifdef ARGV_USE_ATFUNCTIONS
+# if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+
+/* 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__) || defined(__FreeBSD_kernel__) */
+#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))
+
+STATIC bool
+S_argvout_final(pTHX_ MAGIC *mg, IO *io, bool not_implicit) {
+ bool retval;
+
+ /* ensure args are checked before we start using them */
+ PERL_ARGS_ASSERT_ARGVOUT_FINAL;
+
+ {
+ /* handle to an in-place edit work file */
+ SV **back_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_BACKUP_NAME, FALSE);
+ SV **temp_psv = av_fetch((AV*)mg->mg_obj, ARGVMG_TEMP_NAME, FALSE);
+ /* 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);
+ 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
+ UV mode;
+ int fd;
+
+ const char *orig_pv;
+
+ 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));
+ dfd = my_dirfd(dir);
+#endif
+
+ orig_pv = SvPVX(*orig_psv);
+ mode = SvUV(*mode_psv);
+
+ if ((mode & (S_ISUID|S_ISGID)) != 0
+ && (fd = PerlIO_fileno(IoIFP(io))) >= 0) {
+ (void)PerlIO_flush(IoIFP(io));
+#ifdef HAS_FCHMOD
+ (void)fchmod(fd, mode);
+#else
+ (void)PerlLIO_chmod(orig_pv, mode);
+#endif
+ }
+
+ 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)
+ && IoIFP(GvIOp(PL_argvgv))
+ && (IoFLAGS(GvIOp(PL_argvgv)) & (IOf_ARGV|IOf_START)) == IOf_ARGV) {
+ 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 &&
+ !(UNLIKELY(NotSupported(errno)) &&
+ dir_unchanged(orig_pv, mg) &&
+ link(orig_pv, SvPVX(*back_psv)) == 0)
+# else
+ link(orig_pv, SvPVX(*back_psv)) < 0
+# endif
+ )
+#endif
+ {
+#ifdef HAS_RENAME
+ if (
+# ifdef ARGV_USE_ATFUNCTIONS
+ 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));
+ }
+ /* should we warn here? */
+ goto abort_inplace;
+ }
+#else
+ (void)UNLINK(SvPVX(*back_psv));
+ if (link(orig_pv, SvPVX(*back_psv))) {
+ if (!not_implicit) {
+ Perl_croak(aTHX_ "Can't rename %s to %s: %s, skipping file",
+ SvPVX(*orig_psv), SvPVX(*back_psv), Strerror(errno));
+ }
+ goto abort_inplace;
+ }
+ /* we need to use link() to get the temp into place too, and linK()
+ fails if the new link name exists */
+ (void)UNLINK(orig_pv);
+#endif
+ }
+ }
+#if defined(DOSISH) || defined(__CYGWIN__) || !defined(HAS_RENAME)
+ else {
+ UNLINK(orig_pv);
+ }
+#endif
+ if (
+#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) {
+#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));
+ retval = FALSE;
+ }
+#ifndef HAS_RENAME
+ UNLINK(SvPVX(*temp_psv));
+#endif
+ }
+ else {
+#ifdef ARGV_USE_ATFUNCTIONS
+ if (unlinkat(dfd, SvPVX_const(*temp_psv), 0) &&
+ NotSupported(errno))
+ UNLINK(SvPVX_const(*temp_psv));
+
+#else
+ UNLINK(SvPVX_const(*temp_psv));
+#endif
+ if (!not_implicit) {
+ Perl_croak(aTHX_ "Failed to close in-place work file %s: %s",
+ SvPVX(*temp_psv), Strerror(errno));
+ }
+ }
+ freext:
+ ;
+ }
+ return retval;
+}
+