Index: Doc/library/os.rst =================================================================== --- Doc/library/os.rst (revision 62091) +++ Doc/library/os.rst (working copy) @@ -742,10 +742,16 @@ .. function:: access(path, mode) - Use the real uid/gid to test for access to *path*. Note that most operations + On Unix: Use the real uid/gid to test for access to *path*. Note that most operations will use the effective uid/gid, therefore this routine can be used in a suid/sgid environment to test if the invoking user has the specified access to - *path*. *mode* should be :const:`F_OK` to test the existence of *path*, or it + *path*. + + On Windows: Use the readonly attribute combined with the AccessCheck API to + test for access to *path*. NB :const:`X_OK` only determines whether the file + *may* be executed, not whether it is in fact an executable program. + + *mode* should be :const:`F_OK` to test the existence of *path*, or it can be the inclusive OR of one or more of :const:`R_OK`, :const:`W_OK`, and :const:`X_OK` to test permissions. Return :const:`True` if access is allowed, :const:`False` if not. See the Unix man page :manpage:`access(2)` for more @@ -764,7 +770,6 @@ succeed, particularly for operations on network filesystems which may have permissions semantics beyond the usual POSIX permission-bit model. - .. data:: F_OK Value to pass as the *mode* parameter of :func:`access` to test the existence of Index: Lib/test/test_os.py =================================================================== --- Lib/test/test_os.py (revision 62091) +++ Lib/test/test_os.py (working copy) @@ -18,11 +18,95 @@ os.unlink(test_support.TESTFN) tearDown = setUp - def test_access(self): - f = os.open(test_support.TESTFN, os.O_CREAT|os.O_RDWR) - os.close(f) - self.assert_(os.access(test_support.TESTFN, os.W_OK)) + if sys.platform == 'win32': + def test_access_f(self): + f = os.open(test_support.TESTFN, os.O_CREAT) + os.close(f) + os.system("echo y| cacls %s /D Everyone" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.F_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.F_OK)) + os.system("echo y| cacls %s /G Everyone:R" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.F_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.F_OK)) + os.system("echo y| cacls %s /G Everyone:W" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.F_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.F_OK)) + os.system("echo y| cacls %s /G Everyone:C" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.F_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.F_OK)) + os.unlink(test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.F_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.F_OK)) + def test_access_r(self): + f = os.open(test_support.TESTFN, os.O_CREAT) + os.close(f) + os.system("echo y| cacls %s /D Everyone" % test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.R_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.R_OK)) + os.system("echo y| cacls %s /G Everyone:R" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.R_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.R_OK)) + os.system("echo y| cacls %s /G Everyone:W" % test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.R_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.R_OK)) + os.system("echo y| cacls %s /G Everyone:C" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.R_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.R_OK)) + os.unlink(test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.R_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.R_OK)) + + def test_access_w(self): + f = os.open(test_support.TESTFN, os.O_CREAT) + os.close(f) + os.system("echo y| cacls %s /D Everyone" % test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.W_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.W_OK)) + os.system("echo y| cacls %s /G Everyone:R" % test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.W_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.W_OK)) + os.system("echo y| cacls %s /G Everyone:W" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.W_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.W_OK)) + os.system("attrib +r %s" % test_support.TESTFN) + self.assert_(not os.access(unicode(test_support.TESTFN), os.W_OK)) + os.system("attrib -r %s" % test_support.TESTFN) + os.system("echo y| cacls %s /G Everyone:C" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.W_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.W_OK)) + os.system("attrib +r %s" % test_support.TESTFN) + self.assert_(not os.access(unicode(test_support.TESTFN), os.W_OK)) + os.system("attrib -r %s" % test_support.TESTFN) + os.unlink(test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.W_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.W_OK)) + + def test_access_x(self): + f = os.open(test_support.TESTFN, os.O_CREAT) + os.close(f) + os.system("echo y| cacls %s /D Everyone" % test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.X_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.X_OK)) + os.system("echo y| cacls %s /G Everyone:R" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.X_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.X_OK)) + os.system("echo y| cacls %s /G Everyone:W" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.X_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.X_OK)) + os.system("echo y| cacls %s /G Everyone:C" % test_support.TESTFN) + self.assert_(os.access(test_support.TESTFN, os.X_OK)) + self.assert_(os.access(unicode(test_support.TESTFN), os.X_OK)) + os.unlink(test_support.TESTFN) + self.assert_(not os.access(test_support.TESTFN, os.X_OK)) + self.assert_(not os.access(unicode(test_support.TESTFN), os.X_OK)) + + else: + def test_access(self): + f = os.open(test_support.TESTFN, os.O_CREAT|os.O_RDWR) + os.close(f) + self.assert_(os.access(test_support.TESTFN, os.W_OK)) + def test_closerange(self): f = os.open(test_support.TESTFN, os.O_CREAT|os.O_RDWR) # close a fd that is open, and one that isn't Index: Modules/posixmodule.c =================================================================== --- Modules/posixmodule.c (revision 62091) +++ Modules/posixmodule.c (working copy) @@ -1498,22 +1498,53 @@ /* POSIX methods */ +#ifndef F_OK +#define F_OK 0 +#endif +#ifndef R_OK +#define R_OK 4 +#endif +#ifndef W_OK +#define W_OK 2 +#endif +#ifndef X_OK +#define X_OK 1 +#endif + PyDoc_STRVAR(posix_access__doc__, "access(path, mode) -> True if granted, False otherwise\n\n\ -Use the real uid/gid to test for access to a path. Note that most\n\ +Unix: Use the real uid/gid to test for access to a path. Note that most\n\ operations will use the effective uid/gid, therefore this routine can\n\ be used in a suid/sgid environment to test if the invoking user has the\n\ specified access to the path. The mode argument can be F_OK to test\n\ -existence, or the inclusive-OR of R_OK, W_OK, and X_OK."); +existence, or the inclusive-OR of R_OK, W_OK, and X_OK.\n\n\ +Windows: Use the AccessCheck API to determine what access is allowed\n\ +according to the user's permission and the ACL on the path.\ +Also check the readonly attribute which prevents write access."); static PyObject * posix_access(PyObject *self, PyObject *args) { char *path; int mode; - + + int attr; + SECURITY_INFORMATION requested_information = OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; + PSECURITY_DESCRIPTOR pSD = NULL; + DWORD dwSize = 0; + HANDLE hToken = INVALID_HANDLE_VALUE; + DWORD access_desired = 0; + + GENERIC_MAPPING mapping; + PRIVILEGE_SET privilege_set; + DWORD privilege_set_size = sizeof(privilege_set); + DWORD access_granted = 0; + BOOL is_access_granted = FALSE; + BOOL can_read_access = FALSE; + BOOL impersonating = FALSE; + #ifdef Py_WIN_WIDE_FILENAMES - DWORD attr; if (unicode_file_names()) { PyUnicodeObject *po; if (PyArg_ParseTuple(args, "Ui:access", &po, &mode)) { @@ -1521,30 +1552,102 @@ /* PyUnicode_AS_UNICODE OK without thread lock as it is a simple dereference. */ attr = GetFileAttributesW(PyUnicode_AS_UNICODE(po)); + GetFileSecurityW(PyUnicode_AS_UNICODE(po), + requested_information, 0, 0, &dwSize); + pSD = (PSECURITY_DESCRIPTOR)malloc(dwSize); + can_read_access = GetFileSecurityW(PyUnicode_AS_UNICODE(po), + requested_information, pSD, dwSize, &dwSize); Py_END_ALLOW_THREADS - goto finish; + goto check; } /* Drop the argument parsing error as narrow strings are also valid. */ PyErr_Clear(); } + if (!PyArg_ParseTuple(args, "eti:access", Py_FileSystemDefaultEncoding, &path, &mode)) return 0; Py_BEGIN_ALLOW_THREADS attr = GetFileAttributesA(path); - Py_END_ALLOW_THREADS + GetFileSecurityA (path, requested_information, 0, 0, &dwSize); + pSD = (PSECURITY_DESCRIPTOR)malloc(dwSize); + can_read_access = GetFileSecurityA (path, requested_information, pSD, + dwSize, &dwSize); + Py_END_ALLOW_THREADS PyMem_Free(path); -finish: - if (attr == 0xFFFFFFFF) - /* File does not exist, or cannot read attributes */ + +check: + /* Either the file doesn't exist or we haven't the permission to + read the ACL */ + if ((attr == 0xFFFFFFFF) || !can_read_access) return PyBool_FromLong(0); - /* Access is possible if either write access wasn't requested, or - the file isn't read-only, or if it's a directory, as there are - no read-only directories on Windows. */ - return PyBool_FromLong(!(mode & 2) - || !(attr & FILE_ATTRIBUTE_READONLY) - || (attr & FILE_ATTRIBUTE_DIRECTORY)); + + /* Write access is requested and the (non-directory) path is flagged + as readonly. NB readonly on a directory has a separate meaning cf + http://support.microsoft.com/kb/326549 */ + if ((mode & W_OK) && (attr & FILE_ATTRIBUTE_READONLY) && + !(attr & FILE_ATTRIBUTE_DIRECTORY)) + return PyBool_FromLong(0); + + /* If we're only looking for existence then we're done; no need + to check the security. + */ + if (mode == F_OK) { + return PyBool_FromLong(1); + } + + /* Get hold of a token containing the logged-on user's authorization + */ + if (!IsValidSecurityDescriptor(pSD)) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, GetLastError()); + goto release; + } + if (!ImpersonateSelf(SecurityImpersonation)) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, GetLastError()); + goto release; + } + impersonating = TRUE; + if (!OpenThreadToken(GetCurrentThread (), TOKEN_ALL_ACCESS, + TRUE, &hToken)) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, GetLastError()); + goto release; + } + + /* Convert generic access bits into file-specific ones + and check whether the access requested is allowed. + */ + access_desired = 0; + if (mode & X_OK) + access_desired |= FILE_EXECUTE; + if (mode & R_OK) + access_desired |= FILE_READ_DATA; + if (mode & W_OK) + access_desired |= FILE_WRITE_DATA; + mapping.GenericRead = FILE_READ_DATA; + mapping.GenericWrite = FILE_WRITE_DATA; + mapping.GenericExecute = FILE_EXECUTE; + mapping.GenericAll = FILE_ALL_ACCESS; + MapGenericMask(&access_desired, &mapping); + + if (!AccessCheck( + pSD, hToken, access_desired, &mapping, &privilege_set, + &privilege_set_size, &access_granted, &is_access_granted)) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, GetLastError()); + goto release; + } +release: + if (impersonating) + RevertToSelf(); + if (pSD) + free(pSD); + if (hToken != INVALID_HANDLE_VALUE) + CloseHandle(hToken); + + if (PyErr_Occurred()) + return NULL; + else + return PyBool_FromLong(is_access_granted); #else int res; if (!PyArg_ParseTuple(args, "eti:access", @@ -1558,19 +1661,6 @@ #endif } -#ifndef F_OK -#define F_OK 0 -#endif -#ifndef R_OK -#define R_OK 4 -#endif -#ifndef W_OK -#define W_OK 2 -#endif -#ifndef X_OK -#define X_OK 1 -#endif - #ifdef HAVE_TTYNAME PyDoc_STRVAR(posix_ttyname__doc__, "ttyname(fd) -> string\n\n\