diff -r 2b54e25d6ecb Modules/posixmodule.c --- a/Modules/posixmodule.c Sun Jun 24 04:37:41 2012 +0200 +++ b/Modules/posixmodule.c Sat Jun 23 21:51:46 2012 -0700 @@ -719,13 +719,8 @@ } static int -follow_symlinks_specified(char *function_name, int follow_symlinks) { - if (follow_symlinks) - return 0; - - argument_unavailable_error(function_name, "follow_symlinks"); - return 1; -} +follow_symlinks_specified(char *function_name, path_t *path, int dir_fd, + int *follow_symlinks); static int path_and_dir_fd_invalid(char *function_name, path_t *path, int dir_fd) { @@ -1872,6 +1867,60 @@ #endif /* MS_WINDOWS */ + +static int +follow_symlinks_specified(char *function_name, path_t *path, int dir_fd, + int *follow_symlinks) { + if (*follow_symlinks) + return 0; + +#if defined(HAVE_LSTAT) || defined(MS_WINDOWS) + { + /* + * okay, they specified follow_symlinks=False. + * maybe if we can salvage this. + * is the path they're examining not actually a symlink? + */ + + STRUCT_STAT st; + int result; + + Py_BEGIN_ALLOW_THREADS +#ifdef MS_WINDOWS + if (path->wide) + result = win32_lstat_w(path->wide, &st); + else +#endif + if ((dir_fd != DEFAULT_DIR_FD) && path->narrow) +#ifdef HAVE_FSTATAT + result = fstatat(dir_fd, path->narrow, &st, AT_SYMLINK_NOFOLLOW); +#else + result = 1; +#endif + else if (path->narrow) + result = LSTAT(path->narrow, &st); + else + result = 1; + Py_END_ALLOW_THREADS + + #ifndef S_ISLNK + #define S_ISLNK(mode) ((mode & 0170000) == 0120000) + #endif + + if (!(result || S_ISLNK(st.st_mode))) { + /* it's not! pretend they didn't ask for nofollow. */ + *follow_symlinks = 1; + return 0; + } + + } +#endif + + argument_unavailable_error(function_name, "follow_symlinks"); + return 1; +} + + PyDoc_STRVAR(stat_result__doc__, "stat_result: Result from stat, fstat, or lstat.\n\n\ This object may be accessed either as a tuple of\n\ @@ -2237,16 +2286,23 @@ STRUCT_STAT st; int result; -#if !defined(MS_WINDOWS) && !defined(HAVE_FSTATAT) && !defined(HAVE_LSTAT) - if (follow_symlinks_specified(function_name, follow_symlinks)) - return NULL; -#endif - if (path_and_dir_fd_invalid("stat", path, dir_fd) || dir_fd_and_fd_invalid("stat", dir_fd, path->fd) || fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks)) return NULL; +#if !defined(MS_WINDOWS) && !defined(HAVE_FSTATAT) && !defined(HAVE_LSTAT) + if (!follow_symlinks) { + /* + * we have no fallback here; if we don't have an lstat, + * we can't tell if the path *isn't* a link, thus letting us + * failover to normal stat. + */ + argument_unavailable_error("stat", "follow_symlinks"); + return NULL; + } +#endif + Py_BEGIN_ALLOW_THREADS if (path->fd != -1) result = FSTAT(path->fd, &st); @@ -2403,12 +2459,24 @@ return NULL; #ifndef HAVE_FACCESSAT - if (follow_symlinks_specified("access", follow_symlinks)) + if (follow_symlinks_specified("access", &path, dir_fd, &follow_symlinks)) goto exit; if (effective_ids) { - argument_unavailable_error("access", "effective_ids"); - goto exit; +#if (defined(HAVE_GETEGID) && defined(HAVE_GETGID) && \ + defined(HAVE_GETEUID) && defined(HAVE_GETUID)) + /* + * The user asked that we do this as the effective ids. + * We don't have faccessat(), so we can't do that. + * But, if the effective ids == the real ids, we can + * just use normal access! + */ + if ((getuid() != geteuid()) || (getgid() != getegid())) +#endif + { + argument_unavailable_error("access", "effective_ids"); + goto exit; + } } #endif @@ -2651,89 +2719,106 @@ return NULL; #if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD)) - if (follow_symlinks_specified("chmod", follow_symlinks)) + if (follow_symlinks_specified("chmod", &path, dir_fd, &follow_symlinks)) goto exit; #endif + /* + * this for loop is only in case we need to retry + * after the follow_symlinks_specified call below. + * in all other cases we don't loop. + * (this was marginally preferable to a naked goto.) + */ + for (;;) { + #ifdef MS_WINDOWS - Py_BEGIN_ALLOW_THREADS - if (path.wide) - attr = GetFileAttributesW(path.wide); - else - attr = GetFileAttributesA(path.narrow); - if (attr == 0xFFFFFFFF) - result = 0; - else { - if (mode & _S_IWRITE) - attr &= ~FILE_ATTRIBUTE_READONLY; + Py_BEGIN_ALLOW_THREADS + if (path.wide) + attr = GetFileAttributesW(path.wide); else - attr |= FILE_ATTRIBUTE_READONLY; - if (path.wide) - result = SetFileAttributesW(path.wide, attr); + attr = GetFileAttributesA(path.narrow); + if (attr == 0xFFFFFFFF) + result = 0; + else { + if (mode & _S_IWRITE) + attr &= ~FILE_ATTRIBUTE_READONLY; + else + attr |= FILE_ATTRIBUTE_READONLY; + if (path.wide) + result = SetFileAttributesW(path.wide, attr); + else + result = SetFileAttributesA(path.narrow, attr); + } + Py_END_ALLOW_THREADS + + if (!result) { + return_value = win32_error_object("chmod", path.object); + goto exit; + } +#else /* MS_WINDOWS */ + Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_FCHMOD + if (path.fd != -1) + result = fchmod(path.fd, mode); else - result = SetFileAttributesA(path.narrow, attr); - } - Py_END_ALLOW_THREADS - - if (!result) { - return_value = win32_error_object("chmod", path.object); - goto exit; - } -#else /* MS_WINDOWS */ - Py_BEGIN_ALLOW_THREADS -#ifdef HAVE_FCHMOD - if (path.fd != -1) - result = fchmod(path.fd, mode); - else #endif #ifdef HAVE_LCHMOD - if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) - result = lchmod(path.narrow, mode); - else + if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) + result = lchmod(path.narrow, mode); + else #endif #ifdef HAVE_FCHMODAT - if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks) { - /* - * fchmodat() doesn't currently support AT_SYMLINK_NOFOLLOW! - * The documentation specifically shows how to use it, - * and then says it isn't implemented yet. - * (true on linux with glibc 2.15, and openindiana 3.x) - * - * Once it is supported, os.chmod will automatically - * support dir_fd and follow_symlinks=False. (Hopefully.) - * Until then, we need to be careful what exception we raise. - */ - result = fchmodat(dir_fd, path.narrow, mode, - follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); - /* - * But wait! We can't throw the exception without allowing threads, - * and we can't do that in this nested scope. (Macro trickery, sigh.) - */ - fchmodat_nofollow_unsupported = - result && - ((errno == ENOTSUP) || (errno == EOPNOTSUPP)) && - !follow_symlinks; - } - else -#endif - result = chmod(path.narrow, mode); - Py_END_ALLOW_THREADS - - if (result) { + if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks) { + /* + * fchmodat() doesn't currently support AT_SYMLINK_NOFOLLOW! + * The documentation specifically shows how to use it, + * and then says it isn't implemented yet. + * (true on linux with glibc 2.15, and openindiana 3.x) + * + * Once it is supported, os.chmod will automatically + * support dir_fd and follow_symlinks=False. (Hopefully.) + * Until then, we need to be careful what exception we raise. + */ + result = fchmodat(dir_fd, path.narrow, mode, + follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); + /* + * But wait! We can't throw the exception without allowing threads, + * and we can't do that in this nested scope. (Macro trickery, sigh.) + */ + fchmodat_nofollow_unsupported = + result && + ((errno == ENOTSUP) || (errno == EOPNOTSUPP)) && + !follow_symlinks; + } + else +#endif + result = chmod(path.narrow, mode); + Py_END_ALLOW_THREADS + + if (result) { #ifdef HAVE_FCHMODAT - if (fchmodat_nofollow_unsupported) { - if (dir_fd != DEFAULT_DIR_FD) - dir_fd_and_follow_symlinks_invalid("chmod", - dir_fd, follow_symlinks); + if (fchmodat_nofollow_unsupported) { + if (dir_fd != DEFAULT_DIR_FD) + dir_fd_and_follow_symlinks_invalid("chmod", dir_fd, + follow_symlinks); + else { + /* + * we failed only because of follow_symlinks! + * maybe we can retry! + */ + if (!follow_symlinks_specified("chmod", &path, dir_fd, + &follow_symlinks)) + continue; + } + } else - follow_symlinks_specified("chmod", follow_symlinks); - } - else -#endif - return_value = path_error("chmod", &path); - goto exit; - } -#endif +#endif + return_value = path_error("chmod", &path); + goto exit; + } +#endif + break; + } Py_INCREF(Py_None); return_value = Py_None; @@ -2821,7 +2906,7 @@ return NULL; #ifndef HAVE_LCHFLAGS - if (follow_symlinks_specified("chflags", follow_symlinks)) + if (follow_symlinks_specified("chflags", &path, DEFAULT_DIR_FD, &follow_symlinks)) goto exit; #endif @@ -2982,14 +3067,15 @@ &follow_symlinks)) return NULL; -#if !(defined(HAVE_LCHOWN) || defined(HAVE_FCHOWNAT)) - if (follow_symlinks_specified("chown", follow_symlinks)) - goto exit; -#endif if (dir_fd_and_fd_invalid("chown", dir_fd, path.fd) || fd_and_follow_symlinks_invalid("chown", path.fd, follow_symlinks)) goto exit; +#if !(defined(HAVE_LCHOWN) || defined(HAVE_FCHOWNAT)) + if (follow_symlinks_specified("chown", &path, dir_fd, &follow_symlinks)) + goto exit; +#endif + #ifdef __APPLE__ /* * This is for Mac OS X 10.3, which doesn't have lchown. @@ -2998,8 +3084,8 @@ * of a graceful failover. */ if ((!follow_symlinks) && (lchown == NULL)) { - follow_symlinks_specified("chown", follow_symlinks); - goto exit; + if (follow_symlinks_specified("chown", &path, dir_fd, &follow_symlinks)) + goto exit; } #endif @@ -4598,16 +4684,16 @@ utime.now = 1; } -#if !UTIME_HAVE_NOFOLLOW_SYMLINKS - if (follow_symlinks_specified("utime", follow_symlinks)) - goto exit; -#endif - if (path_and_dir_fd_invalid("utime", &path, dir_fd) || dir_fd_and_fd_invalid("utime", dir_fd, path.fd) || fd_and_follow_symlinks_invalid("utime", path.fd, follow_symlinks)) goto exit; +#if !UTIME_HAVE_NOFOLLOW_SYMLINKS + if (follow_symlinks_specified("utime", &path, dir_fd, &follow_symlinks)) + goto exit; +#endif + #if !defined(HAVE_UTIMENSAT) if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { PyErr_SetString(PyExc_RuntimeError, @@ -11971,7 +12057,8 @@ { int ignored; fd_specified("", -1); - follow_symlinks_specified("", 1); + ignored = 1; + follow_symlinks_specified("", NULL, DEFAULT_DIR_FD, &ignored); dir_fd_and_follow_symlinks_invalid("chmod", DEFAULT_DIR_FD, 1); dir_fd_converter(Py_None, &ignored); dir_fd_unavailable(Py_None, &ignored);