/* Copyright (C) 2011 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1997 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #include #include #include #include #include #include #include #include #include #include #include #include int _rename(const char *old, const char *_new) { __dpmi_regs r = {0}; int i; int olen = strlen(old) + 1; int use_lfn = _USE_LFN; char tempfile[FILENAME_MAX], tempfile1[FILENAME_MAX]; const char *orig = old; int lfn_fd = -1; int identical_but_for_case = 0; if (__file_exists(_new) && strcmp(_truename(old, tempfile), _truename(_new, tempfile1)) == 0) { /* NEW might seem to exist because the filesystem is case- insensitive, if OLD and NEW are identical but for the letter case. Allow renaming `foo' into `Foo' under LFN. */ if (use_lfn) { const char *s1 = old + strlen(old), *s2 = _new + strlen(_new); /* Find the basename of both OLD and NEW. */ while (s1 > old && s2 > _new && strchr(":/\\", s1[-1]) == NULL && strchr(":/\\", s2[-1]) == NULL) { s1--; s2--; } /* If OLD and NEW are really the same file, do nothing. */ if (strcmp(s1, s2) == 0) return 0; identical_but_for_case = 1; } else return 0; /* no LFN; foo and Foo are *always* the same file */ } r.x.dx = __tb_offset; r.x.di = __tb_offset + olen; r.x.ds = r.x.es = __tb_segment; if (use_lfn && !identical_but_for_case) { /* Windows 95 bug: for some filenames, when you rename file -> file~ (as in Emacs, to leave a backup), the short 8+3 alias doesn't change, which effectively makes OLD and NEW the same file. We must rename through a temporary file to work around this. */ char *pbase = 0, *p; static char try_char[] = "abcdefghijklmnopqrstuvwxyz012345789"; int idx = sizeof(try_char) - 1; /* Generate a temporary name. Can't use `tmpnam', since $TMPDIR might point to another drive, which will fail the DOS call. */ strcpy(tempfile, old); for (p = tempfile; *p; p++) /* ensure temporary is on the same drive */ if (*p == '/' || *p == '\\' || *p == ':') pbase = p; if (pbase) pbase++; else pbase = tempfile; strcpy(pbase, "X$$djren$$.$$temp$$"); do { if (idx <= 0) return -1; *pbase = try_char[--idx]; } while (_chmod(tempfile, 0) != -1); r.x.flags = 1; /* Always set CF before calling a 0x71NN function. */ r.x.ax = 0x7156; _put_path2(tempfile, olen); _put_path(old); __dpmi_int(0x21, &r); if ((r.x.flags & 1) || (r.x.ax == 0x7100)) { /* Never assume that the complete LFN API is implemented, so check that AX != 0x7100. E.G.: MSDOS 6.22 and DOSLFN 0.40. If not supported fail. */ errno = __doserr_to_errno(r.x.ax); return -1; } /* Now create a file with the original name. This will ensure that NEW will always have a 8+3 alias different from that of OLD. (Seems to be required when NameNumericTail in the Registry is set to 0.) */ lfn_fd = _creat(old, 0); olen = strlen(tempfile) + 1; old = tempfile; r.x.di = __tb_offset + olen; } for (i = 0; i < 2; i++) { if (use_lfn) { r.x.flags |= 1; /* Always set CF before calling a 0x71NN function. */ r.x.ax = 0x7156; } #if 0 /* It seems that no version of DOS, including DOS 8, which is part of Windows/ME, implements this function. Without LFN, this fails _rename on Windows/ME. Disabled. */ else if ((_osmajor > 7 && _osmajor < 10) /* OS/2 returns v10 and above */ || (_osmajor == 7 && _osminor >= 20)) { /* DOS 7.20 (Windows 98) and later supports a new function with a maximum path length of 128 characters instead of 67. This is important for deeply-nested directories. */ r.x.ax = 0x43ff; r.x.bp = 0x5053; r.h.cl = 0x56; } #endif else r.h.ah = 0x56; _put_path2(_new, olen); _put_path(old); __dpmi_int(0x21, &r); if ((r.x.flags & 1) || (r.x.ax == 0x7100)) { /* Never assume that the complete LFN API is implemented, so check that AX != 0x7100. E.G.: MSDOS 6.22 and DOSLFN 0.40. If not supported fail. */ if (i == 0 && !identical_but_for_case /* don't nuke OLD! */ && (r.x.ax == 5 || (r.x.ax == 2 && __file_exists(old)) /* Windows 2000 returns B7h when the target file exists. */ || r.x.ax == 0xb7)) remove(_new); /* and try again */ else { errno = __doserr_to_errno(r.x.ax); /* Restore to original name if we renamed it to temporary. */ if (use_lfn && !identical_but_for_case) { if (lfn_fd != -1) { _close (lfn_fd); remove (orig); } _put_path2(orig, olen); _put_path(tempfile); r.x.ax = 0x7156; __dpmi_int(0x21, &r); } return -1; } } else break; } /* Success. Delete the file possibly created to work around the Windows 95 bug. */ if (lfn_fd != -1) return (_close (lfn_fd) == 0) ? remove (orig) : -1; return 0; } #ifdef TEST #include int main(int argc, char *argv[]) { if (argc > 2) { printf ("%s -> %s: ", argv[1], argv[2]); if (_rename (argv[1], argv[2])) printf ("%s\n", strerror (errno)); else printf ("Done\n"); } return 0; } #endif