diff options
author | Larry Hastings <larry@hastings.org> | 2012-06-22 23:30:09 (GMT) |
---|---|---|
committer | Larry Hastings <larry@hastings.org> | 2012-06-22 23:30:09 (GMT) |
commit | 9cf065cfdc4245ea7e31edcb2e6ede0cea47d148 (patch) | |
tree | 22d8450865a023586034d555ae72e3753f95e84c /Modules/posixmodule.c | |
parent | f0f4742b495554238d1204ce0002c1ef1ba23507 (diff) | |
download | cpython-9cf065cfdc4245ea7e31edcb2e6ede0cea47d148.zip cpython-9cf065cfdc4245ea7e31edcb2e6ede0cea47d148.tar.gz cpython-9cf065cfdc4245ea7e31edcb2e6ede0cea47d148.tar.bz2 |
Issue #14626: Large refactoring of functions / parameters in the os module.
Many functions now support "dir_fd" and "follow_symlinks" parameters;
some also support accepting an open file descriptor in place of of a path
string. Added os.support_* collections as LBYL helpers. Removed many
functions only previously seen in 3.3 alpha releases (often starting with
"f" or "l", or ending with "at"). Originally suggested by Serhiy Storchaka;
implemented by Larry Hastings.
Diffstat (limited to 'Modules/posixmodule.c')
-rw-r--r-- | Modules/posixmodule.c | 4846 |
1 files changed, 2528 insertions, 2318 deletions
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 5418cb9..69c5e05 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -379,10 +379,12 @@ static int win32_can_symlink = 0; #undef STRUCT_STAT #if defined(MS_WIN64) || defined(MS_WINDOWS) # define STAT win32_stat +# define LSTAT win32_lstat # define FSTAT win32_fstat # define STRUCT_STAT struct win32_stat #else # define STAT stat +# define LSTAT lstat # define FSTAT fstat # define STRUCT_STAT struct stat #endif @@ -398,6 +400,379 @@ static int win32_can_symlink = 0; #endif #endif + +#ifdef MS_WINDOWS +static int +win32_warn_bytes_api() +{ + return PyErr_WarnEx(PyExc_DeprecationWarning, + "The Windows bytes API has been deprecated, " + "use Unicode filenames instead", + 1); +} +#endif + + +#ifdef AT_FDCWD +#define DEFAULT_DIR_FD AT_FDCWD +#else +#define DEFAULT_DIR_FD (-100) +#endif + +static int +_fd_converter(PyObject *o, int *p, int default_value) { + long long_value; + if (o == Py_None) { + *p = default_value; + return 1; + } + if (PyFloat_Check(o)) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + return 0; + } + long_value = PyLong_AsLong(o); + if (long_value == -1 && PyErr_Occurred()) + return 0; + if (long_value > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "signed integer is greater than maximum"); + return 0; + } + if (long_value < INT_MIN) { + PyErr_SetString(PyExc_OverflowError, + "signed integer is less than minimum"); + return 0; + } + *p = (int)long_value; + return 1; +} + +static int +dir_fd_converter(PyObject *o, void *p) { + return _fd_converter(o, (int *)p, DEFAULT_DIR_FD); +} + + + +/* + * A PyArg_ParseTuple "converter" function + * that handles filesystem paths in the manner + * preferred by the os module. + * + * path_converter accepts (Unicode) strings and their + * subclasses, and bytes and their subclasses. What + * it does with the argument depends on the platform: + * + * * On Windows, if we get a (Unicode) string we + * extract the wchar_t * and return it; if we get + * bytes we extract the char * and return that. + * + * * On all other platforms, strings are encoded + * to bytes using PyUnicode_FSConverter, then we + * extract the char * from the bytes object and + * return that. + * + * path_converter also optionally accepts signed + * integers (representing open file descriptors) instead + * of path strings. + * + * Input fields: + * path.nullable + * If nonzero, the path is permitted to be None. + * path.allow_fd + * If nonzero, the path is permitted to be a file handle + * (a signed int) instead of a string. + * path.function_name + * If non-NULL, path_converter will use that as the name + * of the function in error messages. + * (If path.argument_name is NULL it omits the function name.) + * path.argument_name + * If non-NULL, path_converter will use that as the name + * of the parameter in error messages. + * (If path.argument_name is NULL it uses "path".) + * + * Output fields: + * path.wide + * Points to the path if it was expressed as Unicode + * and was not encoded. (Only used on Windows.) + * path.narrow + * Points to the path if it was expressed as bytes, + * or it was Unicode and was encoded to bytes. + * path.fd + * Contains a file descriptor if path.accept_fd was true + * and the caller provided a signed integer instead of any + * sort of string. + * + * WARNING: if your "path" parameter is optional, and is + * unspecified, path_converter will never get called. + * So if you set allow_fd, you *MUST* initialize path.fd = -1 + * yourself! + * path.length + * The length of the path in characters, if specified as + * a string. + * path.object + * The original object passed in. + * path.cleanup + * For internal use only. May point to a temporary object. + * (Pay no attention to the man behind the curtain.) + * + * At most one of path.wide or path.narrow will be non-NULL. + * If path was None and path.nullable was set, + * or if path was an integer and path.allow_fd was set, + * both path.wide and path.narrow will be NULL + * and path.length will be 0. + * + * path_converter takes care to not write to the path_t + * unless it's successful. However it must reset the + * "cleanup" field each time it's called. + * + * Use as follows: + * path_t path; + * memset(&path, 0, sizeof(path)); + * PyArg_ParseTuple(args, "O&", path_converter, &path); + * // ... use values from path ... + * path_cleanup(&path); + * + * (Note that if PyArg_Parse fails you don't need to call + * path_cleanup(). However it is safe to do so.) + */ +typedef struct { + char *function_name; + char *argument_name; + int nullable; + int allow_fd; + wchar_t *wide; + char *narrow; + int fd; + Py_ssize_t length; + PyObject *object; + PyObject *cleanup; +} path_t; + +static void +path_cleanup(path_t *path) { + if (path->cleanup) { + Py_DECREF(path->cleanup); + path->cleanup = NULL; + } +} + +static int +path_converter(PyObject *o, void *p) { + path_t *path = (path_t *)p; + PyObject *unicode, *bytes; + Py_ssize_t length; + char *narrow; + +#define FORMAT_EXCEPTION(exc, fmt) \ + PyErr_Format(exc, "%s%s" fmt, \ + path->function_name ? path->function_name : "", \ + path->function_name ? ": " : "", \ + path->argument_name ? path->argument_name : "path") + + /* Py_CLEANUP_SUPPORTED support */ + if (o == NULL) { + path_cleanup(path); + return 1; + } + + /* ensure it's always safe to call path_cleanup() */ + path->cleanup = NULL; + + if (o == Py_None) { + if (!path->nullable) { + FORMAT_EXCEPTION(PyExc_TypeError, + "can't specify None for %s argument"); + return 0; + } + path->wide = NULL; + path->narrow = NULL; + path->length = 0; + path->object = o; + path->fd = -1; + return 1; + } + + unicode = PyUnicode_FromObject(o); + if (unicode) { +#ifdef MS_WINDOWS + wchar_t *wide; + length = PyUnicode_GET_SIZE(unicode); + if (length > 32767) { + FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows"); + Py_DECREF(unicode); + return 0; + } + + wide = PyUnicode_AsUnicode(unicode); + if (!wide) { + Py_DECREF(unicode); + return 0; + } + + path->wide = wide; + path->narrow = NULL; + path->length = length; + path->object = o; + path->fd = -1; + path->cleanup = unicode; + return Py_CLEANUP_SUPPORTED; +#else + int converted = PyUnicode_FSConverter(unicode, &bytes); + Py_DECREF(unicode); + if (!converted) + bytes = NULL; +#endif + } + else { + PyErr_Clear(); + bytes = PyBytes_FromObject(o); + if (!bytes) { + PyErr_Clear(); + if (path->allow_fd) { + int fd; + /* + * note: _fd_converter always permits None. + * but we've already done our None check. + * so o cannot be None at this point. + */ + int result = _fd_converter(o, &fd, -1); + if (result) { + path->wide = NULL; + path->narrow = NULL; + path->length = 0; + path->object = o; + path->fd = fd; + return result; + } + } + } + } + + if (!bytes) { + if (!PyErr_Occurred()) + FORMAT_EXCEPTION(PyExc_TypeError, "illegal type for %s parameter"); + return 0; + } + +#ifdef MS_WINDOWS + if (win32_warn_bytes_api()) { + Py_DECREF(bytes); + return 0; + } +#endif + + length = PyBytes_GET_SIZE(bytes); +#ifdef MS_WINDOWS + if (length > MAX_PATH) { + FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows"); + Py_DECREF(bytes); + return 0; + } +#endif + + narrow = PyBytes_AS_STRING(bytes); + if (length != strlen(narrow)) { + FORMAT_EXCEPTION(PyExc_ValueError, "embedded NUL character in %s"); + Py_DECREF(bytes); + return 0; + } + + path->wide = NULL; + path->narrow = narrow; + path->length = length; + path->object = o; + path->fd = -1; + path->cleanup = bytes; + return Py_CLEANUP_SUPPORTED; +} + +static void +argument_unavailable_error(char *function_name, char *argument_name) { + PyErr_Format(PyExc_NotImplementedError, + "%s%s%s unavailable on this platform", + (function_name != NULL) ? function_name : "", + (function_name != NULL) ? ": ": "", + argument_name); +} + +static int +dir_fd_unavailable(PyObject *o, void *p) { + int *dir_fd = (int *)p; + int return_value = _fd_converter(o, dir_fd, DEFAULT_DIR_FD); + if (!return_value) + return 0; + if (*dir_fd == DEFAULT_DIR_FD) + return 1; + argument_unavailable_error(NULL, "dir_fd"); + return 0; +} + +static int +fd_specified(char *function_name, int fd) { + if (fd == -1) + return 0; + + argument_unavailable_error(function_name, "fd"); + return 1; +} + +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; +} + +static int +path_and_dir_fd_invalid(char *function_name, path_t *path, int dir_fd) { + if (!path->narrow && !path->wide && (dir_fd != DEFAULT_DIR_FD)) { + PyErr_Format(PyExc_ValueError, + "%s: can't specify dir_fd without matching path", + function_name); + return 1; + } + return 0; +} + +static int +dir_fd_and_fd_invalid(char *function_name, int dir_fd, int fd) { + if ((dir_fd != DEFAULT_DIR_FD) && (fd != -1)) { + PyErr_Format(PyExc_ValueError, + "%s: can't specify both dir_fd and fd", + function_name); + return 1; + } + return 0; +} + +static int +fd_and_follow_symlinks_invalid(char *function_name, int fd, + int follow_symlinks) { + if ((fd > 0) && (!follow_symlinks)) { + PyErr_Format(PyExc_ValueError, + "%s: cannot use fd and follow_symlinks together", + function_name); + return 1; + } + return 0; +} + +static int +dir_fd_and_follow_symlinks_invalid(char *function_name, int dir_fd, + int follow_symlinks) { + if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { + PyErr_Format(PyExc_ValueError, + "%s: cannot use dir_fd and follow_symlinks together", + function_name); + return 1; + } + return 0; +} + /* A helper used by a number of POSIX-only functions */ #ifndef MS_WINDOWS static int @@ -566,33 +941,6 @@ win32_get_reparse_tag(HANDLE reparse_point_handle, ULONG *reparse_tag) return TRUE; } -static int -win32_warn_bytes_api() -{ - return PyErr_WarnEx(PyExc_DeprecationWarning, - "The Windows bytes API has been deprecated, " - "use Unicode filenames instead", - 1); -} - -static PyObject* -win32_decode_filename(PyObject *obj) -{ - PyObject *unicode; - if (PyUnicode_Check(obj)) { - if (PyUnicode_READY(obj)) - return NULL; - Py_INCREF(obj); - return obj; - } - if (!PyUnicode_FSDecoder(obj, &unicode)) - return NULL; - if (win32_warn_bytes_api()) { - Py_DECREF(unicode); - return NULL; - } - return unicode; -} #endif /* MS_WINDOWS */ /* Return a dictionary corresponding to the POSIX environment table */ @@ -774,6 +1122,32 @@ win32_error_object(char* function, PyObject* filename) #endif /* MS_WINDOWS */ +/* + * Some functions return Win32 errors, others only ever use posix_error + * (this is for backwards compatibility with exceptions) + */ +static PyObject * +path_posix_error(char *function_name, path_t *path) +{ + if (path->narrow) + return posix_error_with_filename(path->narrow); + return posix_error(); +} + +static PyObject * +path_error(char *function_name, path_t *path) +{ +#ifdef MS_WINDOWS + if (path->narrow) + return win32_error(function_name, path->narrow); + if (path->wide) + return win32_error_unicode(function_name, path->wide); + return win32_error(function_name, NULL); +#else + return path_posix_error(function_name, path); +#endif +} + #if defined(PYOS_OS2) /********************************************************************** * Helper Function to Trim and Format OS/2 Messages @@ -892,32 +1266,6 @@ posix_1str(PyObject *args, char *format, int (*func)(const char*)) return Py_None; } -static PyObject * -posix_2str(PyObject *args, - char *format, - int (*func)(const char *, const char *)) -{ - PyObject *opath1 = NULL, *opath2 = NULL; - char *path1, *path2; - int res; - if (!PyArg_ParseTuple(args, format, - PyUnicode_FSConverter, &opath1, - PyUnicode_FSConverter, &opath2)) { - return NULL; - } - path1 = PyBytes_AsString(opath1); - path2 = PyBytes_AsString(opath2); - Py_BEGIN_ALLOW_THREADS - res = (*func)(path1, path2); - Py_END_ALLOW_THREADS - Py_DECREF(opath1); - Py_DECREF(opath2); - if (res != 0) - /* XXX how to report both path1 and path2??? */ - return posix_error(); - Py_INCREF(Py_None); - return Py_None; -} #ifdef MS_WINDOWS static PyObject* @@ -1525,7 +1873,7 @@ win32_fstat(int file_number, struct win32_stat *result) #endif /* MS_WINDOWS */ PyDoc_STRVAR(stat_result__doc__, -"stat_result: Result from stat or lstat.\n\n\ +"stat_result: Result from stat, fstat, or lstat.\n\n\ This object may be accessed either as a tuple of\n\ (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)\n\ or via the attributes st_mode, st_ino, st_dev, st_nlink, st_uid, and so on.\n\ @@ -1879,132 +2227,238 @@ _pystat_fromstructstat(STRUCT_STAT *st) return v; } +/* POSIX methods */ + + static PyObject * -posix_do_stat(PyObject *self, PyObject *args, - char *format, -#ifdef __VMS - int (*statfunc)(const char *, STRUCT_STAT *, ...), -#else - int (*statfunc)(const char *, STRUCT_STAT *), -#endif - char *wformat, - int (*wstatfunc)(const wchar_t *, STRUCT_STAT *)) +posix_do_stat(char *function_name, path_t *path, + int dir_fd, int follow_symlinks) { STRUCT_STAT st; - PyObject *opath; - char *path; - int res; - PyObject *result; - -#ifdef MS_WINDOWS - PyObject *po; - if (PyArg_ParseTuple(args, wformat, &po)) { - wchar_t *wpath = PyUnicode_AsUnicode(po); - if (wpath == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - res = wstatfunc(wpath, &st); - Py_END_ALLOW_THREADS + int result; - if (res != 0) - return win32_error_object("stat", po); - return _pystat_fromstructstat(&st); - } - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); +#if !defined(MS_WINDOWS) && !defined(HAVE_FSTATAT) && !defined(HAVE_LSTAT) + if (follow_symlinks_specified(function_name, follow_symlinks)) + return NULL; #endif - if (!PyArg_ParseTuple(args, format, - PyUnicode_FSConverter, &opath)) + 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; + + Py_BEGIN_ALLOW_THREADS + if (path->fd != -1) + result = FSTAT(path->fd, &st); + else #ifdef MS_WINDOWS - if (win32_warn_bytes_api()) { - Py_DECREF(opath); - return NULL; + if (path->wide) { + if (follow_symlinks) + result = win32_stat_w(path->wide, &st); + else + result = win32_lstat_w(path->wide, &st); } + else #endif - path = PyBytes_AsString(opath); - Py_BEGIN_ALLOW_THREADS - res = (*statfunc)(path, &st); +#if defined(HAVE_LSTAT) || defined(MS_WINDOWS) + if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) + result = LSTAT(path->narrow, &st); + else +#endif +#ifdef HAVE_FSTATAT + if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks) + result = fstatat(dir_fd, path->narrow, &st, + follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); + else +#endif + result = STAT(path->narrow, &st); Py_END_ALLOW_THREADS - if (res != 0) { -#ifdef MS_WINDOWS - result = win32_error("stat", path); + if (result != 0) + return path_error("stat", path); + + return _pystat_fromstructstat(&st); +} + +PyDoc_STRVAR(posix_stat__doc__, +"stat(path, *, dir_fd=None, follow_symlinks=True) -> stat result\n\n\ +Perform a stat system call on the given path.\n\ +\n\ +path may be specified as either a string or as an open file descriptor.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ + dir_fd may not be supported on your platform; if it is unavailable, using\n\ + it will raise a NotImplementedError.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, stat will examine the symbolic link itself instead of the file the\n\ + link points to.\n\ +It is an error to use dir_fd or follow_symlinks when specifying path as\n\ + an open file descriptor."); + +static PyObject * +posix_stat(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *keywords[] = {"path", "dir_fd", "follow_symlinks", NULL}; + path_t path; + int dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 1; + PyObject *return_value; + + memset(&path, 0, sizeof(path)); + path.allow_fd = 1; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&p:stat", keywords, + path_converter, &path, +#ifdef HAVE_FSTATAT + dir_fd_converter, &dir_fd, #else - result = posix_error_with_filename(path); + dir_fd_unavailable, &dir_fd, #endif - } - else - result = _pystat_fromstructstat(&st); - - Py_DECREF(opath); - return result; + &follow_symlinks)) + return NULL; + return_value = posix_do_stat("stat", &path, dir_fd, follow_symlinks); + path_cleanup(&path); + return return_value; } -/* POSIX methods */ +PyDoc_STRVAR(posix_lstat__doc__, +"lstat(path, *, dir_fd=None) -> stat result\n\n\ +Like stat(), but do not follow symbolic links.\n\ +Equivalent to stat(path, follow_symlinks=False)."); + +static PyObject * +posix_lstat(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *keywords[] = {"path", "dir_fd", NULL}; + path_t path; + int dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 0; + PyObject *return_value; + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:lstat", keywords, + path_converter, &path, +#ifdef HAVE_FSTATAT + dir_fd_converter, &dir_fd +#else + dir_fd_unavailable, &dir_fd +#endif + )) + return NULL; + return_value = posix_do_stat("stat", &path, dir_fd, follow_symlinks); + path_cleanup(&path); + return return_value; +} 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\ -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."); +"access(path, mode, *, dir_fd=None, effective_ids=False,\ + follow_symlinks=True)\n\n\ +Use the real uid/gid to test for access to a path. Returns True if granted,\n\ +False otherwise.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +If effective_ids is True, access will use the effective uid/gid instead of\n\ + the real uid/gid.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, access will examine the symbolic link itself instead of the file the\n\ + link points to.\n\ +dir_fd, effective_ids, and follow_symlinks may not be implemented\n\ + on your platform. If they are unavailable, using them will raise a\n\ + NotImplementedError.\n\ +\n\ +Note that most operations will use the effective uid/gid, therefore this\n\ + routine can be used in a suid/sgid environment to test if the invoking user\n\ + has the specified access to the path.\n\ +The mode argument can be F_OK to test existence, or the inclusive-OR\n\ + of R_OK, W_OK, and X_OK."); static PyObject * -posix_access(PyObject *self, PyObject *args) +posix_access(PyObject *self, PyObject *args, PyObject *kwargs) { - const char *path; + static char *keywords[] = {"path", "mode", "dir_fd", "effective_ids", + "follow_symlinks", NULL}; + path_t path; int mode; + int dir_fd = DEFAULT_DIR_FD; + int effective_ids = 0; + int follow_symlinks = 1; + PyObject *return_value = NULL; #ifdef MS_WINDOWS DWORD attr; - PyObject *po; - if (PyArg_ParseTuple(args, "Ui:access", &po, &mode)) { - wchar_t* wpath = PyUnicode_AsUnicode(po); - if (wpath == NULL) - return NULL; - Py_BEGIN_ALLOW_THREADS - attr = GetFileAttributesW(wpath); - Py_END_ALLOW_THREADS - goto finish; - } - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); - if (!PyArg_ParseTuple(args, "yi:access", &path, &mode)) - return NULL; - if (win32_warn_bytes_api()) +#else + int result; +#endif + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&i|$O&pp:access", keywords, + path_converter, &path, &mode, +#ifdef HAVE_FACCESSAT + dir_fd_converter, &dir_fd, +#else + dir_fd_unavailable, &dir_fd, +#endif + &effective_ids, &follow_symlinks)) return NULL; + +#ifndef HAVE_FACCESSAT + if (follow_symlinks_specified("access", follow_symlinks)) + goto exit; + + if (effective_ids) { + argument_unavailable_error("access", "effective_ids"); + goto exit; + } +#endif + +#ifdef MS_WINDOWS Py_BEGIN_ALLOW_THREADS - attr = GetFileAttributesA(path); + if (path.wide != NULL) + attr = GetFileAttributesW(path.wide); + else + attr = GetFileAttributesA(path.narrow); Py_END_ALLOW_THREADS -finish: - if (attr == 0xFFFFFFFF) - /* File does not exist, or cannot read attributes */ - 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)); + + /* + * Access is possible if + * * we didn't get a -1, and + * * write access wasn't requested, + * * or the file isn't read-only, + * * or it's a directory. + * (Directories cannot be read-only on Windows.) + */ + return_value = PyBool_FromLong( + (attr != 0xFFFFFFFF) && + ((mode & 2) || + !(attr & FILE_ATTRIBUTE_READONLY) || + (attr & FILE_ATTRIBUTE_DIRECTORY))); #else - PyObject *opath; - int res; - if (!PyArg_ParseTuple(args, "O&i:access", - PyUnicode_FSConverter, &opath, &mode)) - return NULL; - path = PyBytes_AsString(opath); + Py_BEGIN_ALLOW_THREADS - res = access(path, mode); +#ifdef HAVE_FACCESSAT + if ((dir_fd != DEFAULT_DIR_FD) || + effective_ids || + !follow_symlinks) { + int flags = 0; + if (!follow_symlinks) + flags |= AT_SYMLINK_NOFOLLOW; + if (effective_ids) + flags |= AT_EACCESS; + result = faccessat(dir_fd, path.narrow, mode, flags); + } + else +#endif + result = access(path.narrow, mode); Py_END_ALLOW_THREADS - Py_DECREF(opath); - return PyBool_FromLong(res == 0); + return_value = PyBool_FromLong(!result); #endif + +#ifndef HAVE_FACCESSAT +exit: +#endif + path_cleanup(&path); + return return_value; } #ifndef F_OK @@ -2075,27 +2529,66 @@ posix_ctermid(PyObject *self, PyObject *noargs) PyDoc_STRVAR(posix_chdir__doc__, "chdir(path)\n\n\ -Change the current working directory to the specified path."); +Change the current working directory to the specified path.\n\ +\n\ +path may always be specified as a string.\n\ +On some platforms, path may also be specified as an open file descriptor.\n\ + If this functionality is unavailable, using it raises an exception."); static PyObject * -posix_chdir(PyObject *self, PyObject *args) +posix_chdir(PyObject *self, PyObject *args, PyObject *kwargs) { + path_t path; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", NULL}; + + memset(&path, 0, sizeof(path)); +#ifdef HAVE_FCHDIR + path.allow_fd = 1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&:chdir", keywords, + path_converter, &path + )) + return NULL; + + Py_BEGIN_ALLOW_THREADS #ifdef MS_WINDOWS - return win32_1str(args, "chdir", "y:chdir", win32_chdir, "U:chdir", win32_wchdir); + if (path.wide) + result = win32_wchdir(path.wide); + else + result = win32_chdir(path.narrow); + result = !result; /* on unix, success = 0, on windows, success = !0 */ #elif defined(PYOS_OS2) && defined(PYCC_GCC) - return posix_1str(args, "O&:chdir", _chdir2); -#elif defined(__VMS) - return posix_1str(args, "O&:chdir", (int (*)(const char *))chdir); + result = _chdir2(path.narrow); #else - return posix_1str(args, "O&:chdir", chdir); +#ifdef HAVE_FCHDIR + if (path.fd != -1) + result = fchdir(path.fd); + else #endif + result = chdir(path.narrow); +#endif + Py_END_ALLOW_THREADS + + if (result) { + return_value = path_error("chdir", &path); + goto exit; + } + + return_value = Py_None; + Py_INCREF(Py_None); + +exit: + path_cleanup(&path); + return return_value; } #ifdef HAVE_FCHDIR PyDoc_STRVAR(posix_fchdir__doc__, -"fchdir(fildes)\n\n\ -Change to the directory of the given file descriptor. fildes must be\n\ -opened on a directory, not a file."); +"fchdir(fd)\n\n\ +Change to the directory of the given file descriptor. fd must be\n\ +opened on a directory, not a file. Equivalent to os.chdir(fd)."); static PyObject * posix_fchdir(PyObject *self, PyObject *fdobj) @@ -2106,87 +2599,152 @@ posix_fchdir(PyObject *self, PyObject *fdobj) PyDoc_STRVAR(posix_chmod__doc__, -"chmod(path, mode)\n\n\ -Change the access permissions of a file."); +"chmod(path, mode, *, dir_fd=None, follow_symlinks=True)\n\n\ +Change the access permissions of a file.\n\ +\n\ +path may always be specified as a string.\n\ +On some platforms, path may also be specified as an open file descriptor.\n\ + If this functionality is unavailable, using it raises an exception.\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, chmod will modify the symbolic link itself instead of the file the\n\ + link points to.\n\ +It is an error to use dir_fd or follow_symlinks when specifying path as\n\ + an open file descriptor.\n\ +dir_fd and follow_symlinks may not be implemented on your platform.\n\ + If they are unavailable, using them will raise a NotImplementedError."); + +static PyObject * +posix_chmod(PyObject *self, PyObject *args, PyObject *kwargs) +{ + path_t path; + int mode; + int dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 1; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "mode", "dir_fd", + "follow_symlinks", NULL}; -static PyObject * -posix_chmod(PyObject *self, PyObject *args) -{ - PyObject *opath = NULL; - const char *path = NULL; - int i; - int res; #ifdef MS_WINDOWS DWORD attr; - PyObject *po; - if (PyArg_ParseTuple(args, "Ui|:chmod", &po, &i)) { - wchar_t *wpath = PyUnicode_AsUnicode(po); - if (wpath == NULL) - return NULL; - Py_BEGIN_ALLOW_THREADS - attr = GetFileAttributesW(wpath); - if (attr != 0xFFFFFFFF) { - if (i & _S_IWRITE) - attr &= ~FILE_ATTRIBUTE_READONLY; - else - attr |= FILE_ATTRIBUTE_READONLY; - res = SetFileAttributesW(wpath, attr); - } - else - res = 0; - Py_END_ALLOW_THREADS - if (!res) - return win32_error_object("chmod", po); - Py_INCREF(Py_None); - return Py_None; - } - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); +#endif - if (!PyArg_ParseTuple(args, "yi:chmod", &path, &i)) - return NULL; - if (win32_warn_bytes_api()) +#ifdef HAVE_FCHMODAT + int fchmodat_nofollow_unsupported = 0; +#endif + + memset(&path, 0, sizeof(path)); +#ifdef HAVE_FCHMOD + path.allow_fd = 1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&i|$O&p:chmod", keywords, + path_converter, &path, + &mode, +#ifdef HAVE_FCHMODAT + dir_fd_converter, &dir_fd, +#else + dir_fd_unavailable, &dir_fd, +#endif + &follow_symlinks)) return NULL; + +#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD)) + if (follow_symlinks_specified("chmod", follow_symlinks)) + goto exit; +#endif + +#ifdef MS_WINDOWS Py_BEGIN_ALLOW_THREADS - attr = GetFileAttributesA(path); - if (attr != 0xFFFFFFFF) { - if (i & _S_IWRITE) + 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; else attr |= FILE_ATTRIBUTE_READONLY; - res = SetFileAttributesA(path, attr); + if (path.wide) + result = SetFileAttributesW(path.wide, attr); + else + result = SetFileAttributesA(path.narrow, attr); } - else - res = 0; Py_END_ALLOW_THREADS - if (!res) { - win32_error("chmod", path); - return NULL; + + if (!result) { + return_value = win32_error_object("chmod", path.object); + goto exit; } - Py_INCREF(Py_None); - return Py_None; #else /* MS_WINDOWS */ - if (!PyArg_ParseTuple(args, "O&i:chmod", PyUnicode_FSConverter, - &opath, &i)) - return NULL; - path = PyBytes_AsString(opath); Py_BEGIN_ALLOW_THREADS - res = chmod(path, i); +#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 +#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. (glibc 2.15) + * + * 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) && !follow_symlinks; + } + else +#endif + result = chmod(path.narrow, mode); Py_END_ALLOW_THREADS - if (res < 0) - return posix_error_with_allocated_filename(opath); - Py_DECREF(opath); - Py_INCREF(Py_None); - return Py_None; + + 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); + else + follow_symlinks_specified("chmod", follow_symlinks); + } + else +#endif + return_value = path_error("chmod", &path); + goto exit; + } #endif + + Py_INCREF(Py_None); + return_value = Py_None; +exit: + path_cleanup(&path); + return return_value; } + #ifdef HAVE_FCHMOD PyDoc_STRVAR(posix_fchmod__doc__, "fchmod(fd, mode)\n\n\ Change the access permissions of the file given by file\n\ -descriptor fd."); +descriptor fd. Equivalent to os.chmod(fd, mode)."); static PyObject * posix_fchmod(PyObject *self, PyObject *args) @@ -2207,7 +2765,8 @@ posix_fchmod(PyObject *self, PyObject *args) PyDoc_STRVAR(posix_lchmod__doc__, "lchmod(path, mode)\n\n\ Change the access permissions of a file. If path is a symlink, this\n\ -affects the link itself rather than the target."); +affects the link itself rather than the target.\n\ +Equivalent to chmod(path, mode, follow_symlinks=False)."); static PyObject * posix_lchmod(PyObject *self, PyObject *args) @@ -2233,28 +2792,56 @@ posix_lchmod(PyObject *self, PyObject *args) #ifdef HAVE_CHFLAGS PyDoc_STRVAR(posix_chflags__doc__, -"chflags(path, flags)\n\n\ -Set file flags."); +"chflags(path, flags, *, follow_symlinks=True)\n\n\ +Set file flags.\n\ +\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, chflags will change flags on the symbolic link itself instead of the\n\ + file the link points to.\n\ +follow_symlinks may not be implemented on your platform. If it is\n\ +unavailable, using it will raise a NotImplementedError."); static PyObject * -posix_chflags(PyObject *self, PyObject *args) +posix_chflags(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *opath; - char *path; + path_t path; unsigned long flags; - int res; - if (!PyArg_ParseTuple(args, "O&k:chflags", - PyUnicode_FSConverter, &opath, &flags)) + int follow_symlinks = 1; + int result; + PyObject *return_value; + static char *keywords[] = {"path", "flags", "follow_symlinks", NULL}; + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&k|$i:chflags", keywords, + path_converter, &path, + &flags, &follow_symlinks)) return NULL; - path = PyBytes_AsString(opath); + +#ifndef HAVE_LCHFLAGS + if (follow_symlinks_specified("chflags", follow_symlinks)) + goto exit; +#endif + Py_BEGIN_ALLOW_THREADS - res = chflags(path, flags); +#ifdef HAVE_LCHFLAGS + if (!follow_symlinks) + result = lchflags(path.narrow, flags); + else +#endif + result = chflags(path.narrow, flags); Py_END_ALLOW_THREADS - if (res < 0) - return posix_error_with_allocated_filename(opath); - Py_DECREF(opath); + + if (result) { + return_value = path_posix_error("chflags", &path); + goto exit; + } + + return_value = Py_None; Py_INCREF(Py_None); - return Py_None; + +exit: + path_cleanup(&path); + return return_value; } #endif /* HAVE_CHFLAGS */ @@ -2262,7 +2849,8 @@ posix_chflags(PyObject *self, PyObject *args) PyDoc_STRVAR(posix_lchflags__doc__, "lchflags(path, flags)\n\n\ Set file flags.\n\ -This function will not follow symbolic links."); +This function will not follow symbolic links.\n\ +Equivalent to chflags(path, flags, follow_symlinks=False)."); static PyObject * posix_lchflags(PyObject *self, PyObject *args) @@ -2346,29 +2934,105 @@ posix_fdatasync(PyObject *self, PyObject *fdobj) #ifdef HAVE_CHOWN PyDoc_STRVAR(posix_chown__doc__, -"chown(path, uid, gid)\n\n\ -Change the owner and group id of path to the numeric uid and gid."); +"chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)\n\n\ +Change the owner and group id of path to the numeric uid and gid.\n\ +\n\ +path may always be specified as a string.\n\ +On some platforms, path may also be specified as an open file descriptor.\n\ + If this functionality is unavailable, using it raises an exception.\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, chown will modify the symbolic link itself instead of the file the\n\ + link points to.\n\ +It is an error to use dir_fd or follow_symlinks when specifying path as\n\ + an open file descriptor.\n\ +dir_fd and follow_symlinks may not be implemented on your platform.\n\ + If they are unavailable, using them will raise a NotImplementedError."); + +static PyObject * +posix_chown(PyObject *self, PyObject *args, PyObject *kwargs) +{ + path_t path; + long uid_l, gid_l; + uid_t uid; + gid_t gid; + int dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 1; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "uid", "gid", "dir_fd", + "follow_symlinks", NULL}; -static PyObject * -posix_chown(PyObject *self, PyObject *args) -{ - PyObject *opath; - char *path; - long uid, gid; - int res; - if (!PyArg_ParseTuple(args, "O&ll:chown", - PyUnicode_FSConverter, &opath, - &uid, &gid)) + memset(&path, 0, sizeof(path)); +#ifdef HAVE_FCHOWN + path.allow_fd = 1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&ll|$O&p:chown", keywords, + path_converter, &path, + &uid_l, &gid_l, +#ifdef HAVE_FCHOWNAT + dir_fd_converter, &dir_fd, +#else + dir_fd_unavailable, &dir_fd, +#endif + &follow_symlinks)) return NULL; - path = PyBytes_AsString(opath); + +#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; + +#ifdef __APPLE__ + /* + * This is for Mac OS X 10.3, which doesn't have lchown. + * (But we still have an lchown symbol because of weak-linking.) + * It doesn't have fchownat either. So there's no possibility + * of a graceful failover. + */ + if ((!follow_symlinks) && (lchown == NULL)) { + follow_symlinks_specified("chown", follow_symlinks); + goto exit; + } +#endif + Py_BEGIN_ALLOW_THREADS - res = chown(path, (uid_t) uid, (gid_t) gid); + uid = (uid_t)uid_l; + gid = (uid_t)gid_l; +#ifdef HAVE_FCHOWN + if (path.fd != -1) + result = fchown(path.fd, uid, gid); + else +#endif +#ifdef HAVE_LCHOWN + if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) + result = lchown(path.narrow, uid, gid); + else +#endif +#ifdef HAVE_FCHOWNAT + if ((dir_fd != DEFAULT_DIR_FD) || (!follow_symlinks)) + result = fchownat(dir_fd, path.narrow, uid, gid, + follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); + else +#endif + result = chown(path.narrow, uid, gid); Py_END_ALLOW_THREADS - if (res < 0) - return posix_error_with_allocated_filename(opath); - Py_DECREF(opath); + + if (result) { + return_value = path_posix_error("chown", &path); + goto exit; + } + + return_value = Py_None; Py_INCREF(Py_None); - return Py_None; + +exit: + path_cleanup(&path); + return return_value; } #endif /* HAVE_CHOWN */ @@ -2376,7 +3040,7 @@ posix_chown(PyObject *self, PyObject *args) PyDoc_STRVAR(posix_fchown__doc__, "fchown(fd, uid, gid)\n\n\ Change the owner and group id of the file given by file descriptor\n\ -fd to the numeric uid and gid."); +fd to the numeric uid and gid. Equivalent to os.chown(fd, uid, gid)."); static PyObject * posix_fchown(PyObject *self, PyObject *args) @@ -2399,7 +3063,8 @@ posix_fchown(PyObject *self, PyObject *args) PyDoc_STRVAR(posix_lchown__doc__, "lchown(path, uid, gid)\n\n\ Change the owner and group id of path to the numeric uid and gid.\n\ -This function will not follow symbolic links."); +This function will not follow symbolic links.\n\ +Equivalent to os.chown(path, uid, gid, follow_symlinks=False)."); static PyObject * posix_lchown(PyObject *self, PyObject *args) @@ -2501,125 +3166,186 @@ posix_getcwd_bytes(PyObject *self) } #endif +#if ((!defined(HAVE_LINK)) && defined(MS_WINDOWS)) +#define HAVE_LINK 1 +#endif #ifdef HAVE_LINK PyDoc_STRVAR(posix_link__doc__, -"link(src, dst)\n\n\ -Create a hard link to a file."); - -static PyObject * -posix_link(PyObject *self, PyObject *args) -{ - return posix_2str(args, "O&O&:link", link); -} -#endif /* HAVE_LINK */ - +"link(src, dst, *, src_dir_fd=None, dst_dir_fd=None, follow_symlinks=True)\n\n\ +Create a hard link to a file.\n\ +\n\ +If either src_dir_fd or dst_dir_fd is not None, it should be a file\n\ + descriptor open to a directory, and the respective path string (src or dst)\n\ + should be relative; the path will then be relative to that directory.\n\ +If follow_symlinks is False, and the last element of src is a symbolic\n\ + link, link will create a link to the symbolic link itself instead of the\n\ + file the link points to.\n\ +src_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your\n\ + platform. If they are unavailable, using them will raise a\n\ + NotImplementedError."); + +static PyObject * +posix_link(PyObject *self, PyObject *args, PyObject *kwargs) +{ + path_t src, dst; + int src_dir_fd = DEFAULT_DIR_FD; + int dst_dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 1; + PyObject *return_value = NULL; + static char *keywords[] = {"src", "dst", "src_dir_fd", "dst_dir_fd", + "follow_symlinks", NULL}; #ifdef MS_WINDOWS -PyDoc_STRVAR(win32_link__doc__, -"link(src, dst)\n\n\ -Create a hard link to a file."); - -static PyObject * -win32_link(PyObject *self, PyObject *args) -{ - PyObject *src, *dst; - BOOL ok; - - if (PyArg_ParseTuple(args, "UU:link", &src, &dst)) - { - wchar_t *wsrc, *wdst; + BOOL result; +#else + int result; +#endif - wsrc = PyUnicode_AsUnicode(src); - if (wsrc == NULL) - goto error; - wdst = PyUnicode_AsUnicode(dst); - if (wdst == NULL) - goto error; + memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|O&O&p:link", keywords, + path_converter, &src, + path_converter, &dst, + dir_fd_converter, &src_dir_fd, + dir_fd_converter, &dst_dir_fd, + &follow_symlinks)) + return NULL; - Py_BEGIN_ALLOW_THREADS - ok = CreateHardLinkW(wdst, wsrc, NULL); - Py_END_ALLOW_THREADS +#ifndef HAVE_LINKAT + if ((src_dir_fd != DEFAULT_DIR_FD) || (dst_dir_fd != DEFAULT_DIR_FD)) { + argument_unavailable_error("link", "src_dir_fd and dst_dir_fd"); + goto exit; + } +#endif - if (!ok) - return win32_error("link", NULL); - Py_RETURN_NONE; + if ((src.narrow && dst.wide) || (src.wide && dst.narrow)) { + PyErr_SetString(PyExc_NotImplementedError, + "link: src and dst must be the same type"); + goto exit; } - else { - PyErr_Clear(); - if (!PyArg_ParseTuple(args, "O&O&:link", - PyUnicode_FSConverter, &src, - PyUnicode_FSConverter, &dst)) - return NULL; - if (win32_warn_bytes_api()) - goto error; +#ifdef MS_WINDOWS + Py_BEGIN_ALLOW_THREADS + if (src.wide) + result = CreateHardLinkW(dst.wide, src.wide, NULL); + else + result = CreateHardLinkA(dst.narrow, src.narrow, NULL); + Py_END_ALLOW_THREADS - Py_BEGIN_ALLOW_THREADS - ok = CreateHardLinkA(PyBytes_AS_STRING(dst), - PyBytes_AS_STRING(src), - NULL); - Py_END_ALLOW_THREADS + if (!result) { + return_value = win32_error_object("link", dst.object); + goto exit; + } +#else + Py_BEGIN_ALLOW_THREADS +#ifndef HAVE_LINKAT + if ((src_dir_fd != DEFAULT_DIR_FD) || + (dst_dir_fd != DEFAULT_DIR_FD) || + (!follow_symlinks)) + result = linkat(src_dir_fd, src.narrow, + dst_dir_fd, dst.narrow, + follow_symlinks ? AT_SYMLINK_FOLLOW : 0); + else +#endif + result = link(src.narrow, dst.narrow); + Py_END_ALLOW_THREADS - Py_XDECREF(src); - Py_XDECREF(dst); + if (result) { + return_value = path_error("link", &dst); + goto exit; + } +#endif - if (!ok) - return win32_error("link", NULL); - Py_RETURN_NONE; + return_value = Py_None; + Py_INCREF(Py_None); - error: - Py_XDECREF(src); - Py_XDECREF(dst); - return NULL; - } +exit: + path_cleanup(&src); + path_cleanup(&dst); + return return_value; } -#endif /* MS_WINDOWS */ +#endif + PyDoc_STRVAR(posix_listdir__doc__, -"listdir([path]) -> list_of_strings\n\n\ +"listdir(path='.') -> list_of_strings\n\n\ Return a list containing the names of the entries in the directory.\n\ \n\ - path: path of directory to list (default: '.')\n\ -\n\ The list is in arbitrary order. It does not include the special\n\ -entries '.' and '..' even if they are present in the directory."); +entries '.' and '..' even if they are present in the directory.\n\ +\n\ +path can always be specified as a string.\n\ +On some platforms, path may also be specified as an open file descriptor.\n\ + If this functionality is unavailable, using it raises NotImplementedError."); static PyObject * -posix_listdir(PyObject *self, PyObject *args) +posix_listdir(PyObject *self, PyObject *args, PyObject *kwargs) { - /* XXX Should redo this putting the (now four) versions of opendir - in separate files instead of having them all here... */ -#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR) + path_t path; + PyObject *list = NULL; + static char *keywords[] = {"path", NULL}; + int fd = -1; - PyObject *d, *v; - HANDLE hFindFile; +#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR) + PyObject *v; + HANDLE hFindFile = INVALID_HANDLE_VALUE; BOOL result; WIN32_FIND_DATA FileData; - const char *path; - Py_ssize_t pathlen; char namebuf[MAX_PATH+5]; /* Overallocate for \\*.*\0 */ char *bufptr = namebuf; - Py_ssize_t len = sizeof(namebuf)-5; /* only claim to have space for MAX_PATH */ - + /* only claim to have space for MAX_PATH */ + Py_ssize_t len = sizeof(namebuf)-5; PyObject *po = NULL; - if (PyArg_ParseTuple(args, "|U:listdir", &po)) { + wchar_t *wnamebuf = NULL; +#elif defined(PYOS_OS2) +#ifndef MAX_PATH +#define MAX_PATH CCHMAXPATH +#endif + char *pt; + PyObject *v; + char namebuf[MAX_PATH+5]; + HDIR hdir = 1; + ULONG srchcnt = 1; + FILEFINDBUF3 ep; + APIRET rc; +#else + PyObject *v; + DIR *dirp = NULL; + struct dirent *ep; + int arg_is_unicode = 1; +#endif + + memset(&path, 0, sizeof(path)); + path.nullable = 1; +#ifdef HAVE_FDOPENDIR + path.allow_fd = 1; + path.fd = -1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O&:listdir", keywords, + path_converter, &path + )) + return NULL; + + /* XXX Should redo this putting the (now four) versions of opendir + in separate files instead of having them all here... */ +#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR) + if (!path.narrow) { WIN32_FIND_DATAW wFileData; - wchar_t *wnamebuf, *po_wchars; + wchar_t *po_wchars; - if (po == NULL) { /* Default arg: "." */ + if (!path.wide) { /* Default arg: "." */ po_wchars = L"."; len = 1; } else { - po_wchars = PyUnicode_AsUnicodeAndSize(po, &len); - if (po_wchars == NULL) - return NULL; + po_wchars = path.wide; + len = wcslen(path.wide); } - /* Overallocate for \\*.*\0 */ + /* The +5 is so we can append "\\*.*\0" */ wnamebuf = malloc((len + 5) * sizeof(wchar_t)); if (!wnamebuf) { PyErr_NoMemory(); - return NULL; + goto exit; } wcscpy(wnamebuf, po_wchars); if (len > 0) { @@ -2628,38 +3354,36 @@ posix_listdir(PyObject *self, PyObject *args) wnamebuf[len++] = L'\\'; wcscpy(wnamebuf + len, L"*.*"); } - if ((d = PyList_New(0)) == NULL) { - free(wnamebuf); - return NULL; + if ((list = PyList_New(0)) == NULL) { + goto exit; } Py_BEGIN_ALLOW_THREADS hFindFile = FindFirstFileW(wnamebuf, &wFileData); Py_END_ALLOW_THREADS if (hFindFile == INVALID_HANDLE_VALUE) { int error = GetLastError(); - if (error == ERROR_FILE_NOT_FOUND) { - free(wnamebuf); - return d; - } - Py_DECREF(d); + if (error == ERROR_FILE_NOT_FOUND) + goto exit; + Py_DECREF(list); + list = NULL; win32_error_unicode("FindFirstFileW", wnamebuf); - free(wnamebuf); - return NULL; + goto exit; } do { /* Skip over . and .. */ if (wcscmp(wFileData.cFileName, L".") != 0 && wcscmp(wFileData.cFileName, L"..") != 0) { - v = PyUnicode_FromWideChar(wFileData.cFileName, wcslen(wFileData.cFileName)); + v = PyUnicode_FromWideChar(wFileData.cFileName, + wcslen(wFileData.cFileName)); if (v == NULL) { - Py_DECREF(d); - d = NULL; + Py_DECREF(list); + list = NULL; break; } - if (PyList_Append(d, v) != 0) { + if (PyList_Append(list, v) != 0) { Py_DECREF(v); - Py_DECREF(d); - d = NULL; + Py_DECREF(list); + list = NULL; break; } Py_DECREF(v); @@ -2670,37 +3394,16 @@ posix_listdir(PyObject *self, PyObject *args) /* FindNextFile sets error to ERROR_NO_MORE_FILES if it got to the end of the directory. */ if (!result && GetLastError() != ERROR_NO_MORE_FILES) { - Py_DECREF(d); - win32_error_unicode("FindNextFileW", wnamebuf); - FindClose(hFindFile); - free(wnamebuf); - return NULL; + Py_DECREF(list); + list = win32_error_unicode("FindNextFileW", wnamebuf); + goto exit; } } while (result == TRUE); - if (FindClose(hFindFile) == FALSE) { - Py_DECREF(d); - win32_error_unicode("FindClose", wnamebuf); - free(wnamebuf); - return NULL; - } - free(wnamebuf); - return d; - } - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); - - if (!PyArg_ParseTuple(args, "y#:listdir", &path, &pathlen)) - return NULL; - if (win32_warn_bytes_api()) - return NULL; - if (pathlen+1 > MAX_PATH) { - PyErr_SetString(PyExc_ValueError, "path too long"); - return NULL; + goto exit; } - strcpy(namebuf, path); - len = pathlen; + strcpy(namebuf, path.narrow); + len = path.length; if (len > 0) { char ch = namebuf[len-1]; if (ch != SEP && ch != ALTSEP && ch != ':') @@ -2708,7 +3411,7 @@ posix_listdir(PyObject *self, PyObject *args) strcpy(namebuf + len, "*.*"); } - if ((d = PyList_New(0)) == NULL) + if ((list = PyList_New(0)) == NULL) return NULL; Py_BEGIN_ALLOW_THREADS @@ -2717,9 +3420,10 @@ posix_listdir(PyObject *self, PyObject *args) if (hFindFile == INVALID_HANDLE_VALUE) { int error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) - return d; - Py_DECREF(d); - return win32_error("FindFirstFile", namebuf); + goto exit; + Py_DECREF(list); + list = win32_error("FindFirstFile", namebuf); + goto exit; } do { /* Skip over . and .. */ @@ -2727,14 +3431,14 @@ posix_listdir(PyObject *self, PyObject *args) strcmp(FileData.cFileName, "..") != 0) { v = PyBytes_FromString(FileData.cFileName); if (v == NULL) { - Py_DECREF(d); - d = NULL; + Py_DECREF(list); + list = NULL; break; } - if (PyList_Append(d, v) != 0) { + if (PyList_Append(list, v) != 0) { Py_DECREF(v); - Py_DECREF(d); - d = NULL; + Py_DECREF(list); + list = NULL; break; } Py_DECREF(v); @@ -2745,46 +3449,33 @@ posix_listdir(PyObject *self, PyObject *args) /* FindNextFile sets error to ERROR_NO_MORE_FILES if it got to the end of the directory. */ if (!result && GetLastError() != ERROR_NO_MORE_FILES) { - Py_DECREF(d); - win32_error("FindNextFile", namebuf); - FindClose(hFindFile); - return NULL; + Py_DECREF(list); + list = win32_error("FindNextFile", namebuf); + goto exit; } } while (result == TRUE); - if (FindClose(hFindFile) == FALSE) { - Py_DECREF(d); - return win32_error("FindClose", namebuf); +exit: + if (hFindFile != INVALID_HANDLE_VALUE) { + if (FindClose(hFindFile) == FALSE) { + if (list != NULL) { + Py_DECREF(list); + list = win32_error_object("FindClose", path.object); + } + } } + if (wnamebuf) + free(wnamebuf); + path_cleanup(&path); - return d; + return list; #elif defined(PYOS_OS2) - -#ifndef MAX_PATH -#define MAX_PATH CCHMAXPATH -#endif - PyObject *oname; - char *name, *pt; - Py_ssize_t len; - PyObject *d, *v; - char namebuf[MAX_PATH+5]; - HDIR hdir = 1; - ULONG srchcnt = 1; - FILEFINDBUF3 ep; - APIRET rc; - - if (!PyArg_ParseTuple(args, "O&:listdir", - PyUnicode_FSConverter, &oname)) - return NULL; - name = PyBytes_AsString(oname); - len = PyBytes_GET_SIZE(oname); - if (len >= MAX_PATH) { - Py_DECREF(oname); + if (path.length >= MAX_PATH) { PyErr_SetString(PyExc_ValueError, "path too long"); - return NULL; + goto exit; } - strcpy(namebuf, name); + strcpy(namebuf, path.narrow); for (pt = namebuf; *pt; pt++) if (*pt == ALTSEP) *pt = SEP; @@ -2792,9 +3483,8 @@ posix_listdir(PyObject *self, PyObject *args) namebuf[len++] = SEP; strcpy(namebuf + len, "*.*"); - if ((d = PyList_New(0)) == NULL) { - Py_DECREF(oname); - return NULL; + if ((list = PyList_New(0)) == NULL) { + goto exit; } rc = DosFindFirst(namebuf, /* Wildcard Pattern to Match */ @@ -2806,7 +3496,9 @@ posix_listdir(PyObject *self, PyObject *args) if (rc != NO_ERROR) { errno = ENOENT; - return posix_error_with_allocated_filename(oname); + Py_DECREF(list); + list = posix_error_with_filename(path.narrow); + goto exit; } if (srchcnt > 0) { /* If Directory is NOT Totally Empty, */ @@ -2822,55 +3514,63 @@ posix_listdir(PyObject *self, PyObject *args) v = PyBytes_FromString(namebuf); if (v == NULL) { - Py_DECREF(d); - d = NULL; + Py_DECREF(list); + list = NULL; break; } - if (PyList_Append(d, v) != 0) { + if (PyList_Append(list, v) != 0) { Py_DECREF(v); - Py_DECREF(d); - d = NULL; + Py_DECREF(list); + list = NULL; break; } Py_DECREF(v); } while (DosFindNext(hdir, &ep, sizeof(ep), &srchcnt) == NO_ERROR && srchcnt > 0); } - Py_DECREF(oname); - return d; +exit: + path_cleanup(&path); + + return list; #else - PyObject *oname; - char *name; - PyObject *d, *v; - DIR *dirp; - struct dirent *ep; - int arg_is_unicode = 1; errno = 0; /* v is never read, so it does not need to be initialized yet. */ - if (!PyArg_ParseTuple(args, "|U:listdir", &v)) { + if (path.narrow && !PyArg_ParseTuple(args, "U:listdir", &v)) { arg_is_unicode = 0; PyErr_Clear(); } - oname = NULL; - if (!PyArg_ParseTuple(args, "|O&:listdir", PyUnicode_FSConverter, &oname)) - return NULL; - if (oname == NULL) { /* Default arg: "." */ - oname = PyBytes_FromString("."); - } - name = PyBytes_AsString(oname); - Py_BEGIN_ALLOW_THREADS - dirp = opendir(name); - Py_END_ALLOW_THREADS - if (dirp == NULL) { - return posix_error_with_allocated_filename(oname); +#ifdef HAVE_FDOPENDIR + if (path.fd != -1) { + /* closedir() closes the FD, so we duplicate it */ + Py_BEGIN_ALLOW_THREADS + fd = dup(path.fd); + Py_END_ALLOW_THREADS + + if (fd == -1) { + list = posix_error(); + goto exit; + } + + Py_BEGIN_ALLOW_THREADS + dirp = fdopendir(fd); + Py_END_ALLOW_THREADS } - if ((d = PyList_New(0)) == NULL) { + else +#endif + { + char *name = path.narrow ? path.narrow : "."; Py_BEGIN_ALLOW_THREADS - closedir(dirp); + dirp = opendir(name); Py_END_ALLOW_THREADS - Py_DECREF(oname); - return NULL; + } + + if (dirp == NULL) { + list = path_error("listdir", &path); + goto exit; + } + if ((list = PyList_New(0)) == NULL) { + goto exit; } for (;;) { errno = 0; @@ -2881,11 +3581,9 @@ posix_listdir(PyObject *self, PyObject *args) if (errno == 0) { break; } else { - Py_BEGIN_ALLOW_THREADS - closedir(dirp); - Py_END_ALLOW_THREADS - Py_DECREF(d); - return posix_error_with_allocated_filename(oname); + Py_DECREF(list); + list = path_error("listdir", &path); + goto exit; } } if (ep->d_name[0] == '.' && @@ -2897,100 +3595,32 @@ posix_listdir(PyObject *self, PyObject *args) else v = PyBytes_FromStringAndSize(ep->d_name, NAMLEN(ep)); if (v == NULL) { - Py_CLEAR(d); + Py_CLEAR(list); break; } - if (PyList_Append(d, v) != 0) { + if (PyList_Append(list, v) != 0) { Py_DECREF(v); - Py_CLEAR(d); + Py_CLEAR(list); break; } Py_DECREF(v); } - Py_BEGIN_ALLOW_THREADS - closedir(dirp); - Py_END_ALLOW_THREADS - Py_DECREF(oname); - - return d; - -#endif /* which OS */ -} /* end of posix_listdir */ - -#ifdef HAVE_FDOPENDIR -PyDoc_STRVAR(posix_flistdir__doc__, -"flistdir(fd) -> list_of_strings\n\n\ -Like listdir(), but uses a file descriptor instead."); -static PyObject * -posix_flistdir(PyObject *self, PyObject *args) -{ - PyObject *d, *v; - DIR *dirp; - struct dirent *ep; - int fd; - - errno = 0; - if (!PyArg_ParseTuple(args, "i:flistdir", &fd)) - return NULL; - /* closedir() closes the FD, so we duplicate it */ - fd = dup(fd); - if (fd < 0) - return posix_error(); - Py_BEGIN_ALLOW_THREADS - dirp = fdopendir(fd); - Py_END_ALLOW_THREADS - if (dirp == NULL) { - close(fd); - return posix_error(); - } - if ((d = PyList_New(0)) == NULL) { +exit: + if (dirp != NULL) { Py_BEGIN_ALLOW_THREADS + if (fd > -1) + rewinddir(dirp); closedir(dirp); Py_END_ALLOW_THREADS - return NULL; - } - for (;;) { - errno = 0; - Py_BEGIN_ALLOW_THREADS - ep = readdir(dirp); - Py_END_ALLOW_THREADS - if (ep == NULL) { - if (errno == 0) { - break; - } else { - Py_BEGIN_ALLOW_THREADS - rewinddir(dirp); - closedir(dirp); - Py_END_ALLOW_THREADS - Py_DECREF(d); - return posix_error(); - } - } - if (ep->d_name[0] == '.' && - (NAMLEN(ep) == 1 || - (ep->d_name[1] == '.' && NAMLEN(ep) == 2))) - continue; - v = PyUnicode_DecodeFSDefaultAndSize(ep->d_name, NAMLEN(ep)); - if (v == NULL) { - Py_CLEAR(d); - break; - } - if (PyList_Append(d, v) != 0) { - Py_DECREF(v); - Py_CLEAR(d); - break; - } - Py_DECREF(v); } - Py_BEGIN_ALLOW_THREADS - rewinddir(dirp); - closedir(dirp); - Py_END_ALLOW_THREADS - return d; -} -#endif + path_cleanup(&path); + + return list; + +#endif /* which OS */ +} /* end of posix_listdir */ #ifdef MS_WINDOWS /* A helper function for abspath on win32 */ @@ -3182,68 +3812,72 @@ check: #endif /* MS_WINDOWS */ PyDoc_STRVAR(posix_mkdir__doc__, -"mkdir(path [, mode=0777])\n\n\ -Create a directory."); +"mkdir(path, mode=0o777, *, dir_fd=None)\n\n\ +Create a directory.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError.\n\ +\n\ +The mode argument is ignored on Windows."); static PyObject * -posix_mkdir(PyObject *self, PyObject *args) +posix_mkdir(PyObject *self, PyObject *args, PyObject *kwargs) { - int res; - const char *path; + path_t path; int mode = 0777; + int dir_fd = DEFAULT_DIR_FD; + static char *keywords[] = {"path", "mode", "dir_fd", NULL}; + PyObject *return_value = NULL; + int result; -#ifdef MS_WINDOWS - PyObject *po; - if (PyArg_ParseTuple(args, "U|i:mkdir", &po, &mode)) - { - wchar_t *wpath = PyUnicode_AsUnicode(po); - if (wpath == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - res = CreateDirectoryW(wpath, NULL); - Py_END_ALLOW_THREADS - if (!res) - return win32_error_object("mkdir", po); - Py_INCREF(Py_None); - return Py_None; - } - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); - if (!PyArg_ParseTuple(args, "y|i:mkdir", &path, &mode)) - return NULL; - if (win32_warn_bytes_api()) + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|i$O&:mkdir", keywords, + path_converter, &path, &mode, +#ifdef HAVE_MKDIRAT + dir_fd_converter, &dir_fd +#else + dir_fd_unavailable, &dir_fd +#endif + )) return NULL; + +#ifdef MS_WINDOWS Py_BEGIN_ALLOW_THREADS - res = CreateDirectoryA(path, NULL); + if (path.wide) + result = CreateDirectoryW(path.wide, NULL); + else + result = CreateDirectoryA(path.narrow, NULL); Py_END_ALLOW_THREADS - if (!res) { - win32_error("mkdir", path); - return NULL; + + if (!result) { + return_value = win32_error_object("mkdir", path.object); + goto exit; } - Py_INCREF(Py_None); - return Py_None; #else - PyObject *opath; - - if (!PyArg_ParseTuple(args, "O&|i:mkdir", - PyUnicode_FSConverter, &opath, &mode)) - return NULL; - path = PyBytes_AsString(opath); Py_BEGIN_ALLOW_THREADS +#if HAVE_MKDIRAT + if (dir_fd != DEFAULT_DIR_FD) + result = mkdirat(dir_fd, path.narrow, mode); + else +#endif #if ( defined(__WATCOMC__) || defined(PYCC_VACPP) ) && !defined(__QNX__) - res = mkdir(path); + result = mkdir(path.narrow); #else - res = mkdir(path, mode); + result = mkdir(path.narrow, mode); #endif Py_END_ALLOW_THREADS - if (res < 0) - return posix_error_with_allocated_filename(opath); - Py_DECREF(opath); - Py_INCREF(Py_None); - return Py_None; + if (result < 0) { + return_value = path_error("mkdir", &path); + goto exit; + } #endif + return_value = Py_None; + Py_INCREF(Py_None); +exit: + path_cleanup(&path); + return return_value; } @@ -3332,85 +3966,118 @@ posix_setpriority(PyObject *self, PyObject *args) static PyObject * -internal_rename(PyObject *self, PyObject *args, int is_replace) +internal_rename(PyObject *args, PyObject *kwargs, int is_replace) { + char *function_name = is_replace ? "replace" : "rename"; + path_t src; + path_t dst; + int src_dir_fd = DEFAULT_DIR_FD; + int dst_dir_fd = DEFAULT_DIR_FD; + int dir_fd_specified; + PyObject *return_value = NULL; + char format[24]; + static char *keywords[] = {"src", "dst", "src_dir_fd", "dst_dir_fd", NULL}; + #ifdef MS_WINDOWS - PyObject *src, *dst; BOOL result; int flags = is_replace ? MOVEFILE_REPLACE_EXISTING : 0; - if (PyArg_ParseTuple(args, - is_replace ? "UU:replace" : "UU:rename", - &src, &dst)) - { - wchar_t *wsrc, *wdst; +#else + int result; +#endif - wsrc = PyUnicode_AsUnicode(src); - if (wsrc == NULL) - return NULL; - wdst = PyUnicode_AsUnicode(dst); - if (wdst == NULL) - return NULL; - Py_BEGIN_ALLOW_THREADS - result = MoveFileExW(wsrc, wdst, flags); - Py_END_ALLOW_THREADS - if (!result) - return win32_error(is_replace ? "replace" : "rename", NULL); - Py_INCREF(Py_None); - return Py_None; + memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); + strcpy(format, "O&O&|$O&O&:"); + strcat(format, function_name); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords, + path_converter, &src, + path_converter, &dst, + dir_fd_converter, &src_dir_fd, + dir_fd_converter, &dst_dir_fd)) + return NULL; + + dir_fd_specified = (src_dir_fd != DEFAULT_DIR_FD) || + (dst_dir_fd != DEFAULT_DIR_FD); +#ifndef HAVE_RENAMEAT + if (dir_fd_specified) { + argument_unavailable_error(function_name, "src_dir_fd and dst_dir_fd"); + goto exit; } - else { - PyErr_Clear(); - if (!PyArg_ParseTuple(args, - is_replace ? "O&O&:replace" : "O&O&:rename", - PyUnicode_FSConverter, &src, - PyUnicode_FSConverter, &dst)) - return NULL; +#endif - if (win32_warn_bytes_api()) - goto error; + if ((src.narrow && dst.wide) || (src.wide && dst.narrow)) { + PyErr_Format(PyExc_ValueError, + "%s: src and dst must be the same type", function_name); + goto exit; + } - Py_BEGIN_ALLOW_THREADS - result = MoveFileExA(PyBytes_AS_STRING(src), - PyBytes_AS_STRING(dst), flags); - Py_END_ALLOW_THREADS +#ifdef MS_WINDOWS + Py_BEGIN_ALLOW_THREADS + if (src.wide) + result = MoveFileExW(src.wide, dst.wide, flags); + else + result = MoveFileExA(src.narrow, dst.narrow, flags); + Py_END_ALLOW_THREADS - Py_XDECREF(src); - Py_XDECREF(dst); + if (!result) { + return_value = win32_error_object(function_name, dst.object); + goto exit; + } - if (!result) - return win32_error(is_replace ? "replace" : "rename", NULL); - Py_INCREF(Py_None); - return Py_None; +#else + Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_RENAMEAT + if (dir_fd_specified) + result = renameat(src_dir_fd, src.narrow, dst_dir_fd, dst.narrow); + else +#endif + result = rename(src.narrow, dst.narrow); + Py_END_ALLOW_THREADS -error: - Py_XDECREF(src); - Py_XDECREF(dst); - return NULL; + if (result) { + return_value = path_error(function_name, &dst); + goto exit; } -#else - return posix_2str(args, - is_replace ? "O&O&:replace" : "O&O&:rename", rename); #endif + + Py_INCREF(Py_None); + return_value = Py_None; +exit: + path_cleanup(&src); + path_cleanup(&dst); + return return_value; } PyDoc_STRVAR(posix_rename__doc__, -"rename(old, new)\n\n\ -Rename a file or directory."); +"rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None)\n\n\ +Rename a file or directory.\n\ +\n\ +If either src_dir_fd or dst_dir_fd is not None, it should be a file\n\ + descriptor open to a directory, and the respective path string (src or dst)\n\ + should be relative; the path will then be relative to that directory.\n\ +src_dir_fd and dst_dir_fd, may not be implemented on your platform.\n\ + If they are unavailable, using them will raise a NotImplementedError."); static PyObject * -posix_rename(PyObject *self, PyObject *args) +posix_rename(PyObject *self, PyObject *args, PyObject *kwargs) { - return internal_rename(self, args, 0); + return internal_rename(args, kwargs, 0); } PyDoc_STRVAR(posix_replace__doc__, -"replace(old, new)\n\n\ -Rename a file or directory, overwriting the destination."); +"replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)\n\n\ +Rename a file or directory, overwriting the destination.\n\ +\n\ +If either src_dir_fd or dst_dir_fd is not None, it should be a file\n\ + descriptor open to a directory, and the respective path string (src or dst)\n\ + should be relative; the path will then be relative to that directory.\n\ +src_dir_fd and dst_dir_fd, may not be implemented on your platform.\n\ + If they are unavailable, using them will raise a NotImplementedError."); static PyObject * -posix_replace(PyObject *self, PyObject *args) +posix_replace(PyObject *self, PyObject *args, PyObject *kwargs) { - return internal_rename(self, args, 1); + return internal_rename(args, kwargs, 1); } PyDoc_STRVAR(posix_rmdir__doc__, @@ -3428,21 +4095,6 @@ posix_rmdir(PyObject *self, PyObject *args) } -PyDoc_STRVAR(posix_stat__doc__, -"stat(path) -> stat result\n\n\ -Perform a stat system call on the given path."); - -static PyObject * -posix_stat(PyObject *self, PyObject *args) -{ -#ifdef MS_WINDOWS - return posix_do_stat(self, args, "O&:stat", STAT, "U:stat", win32_stat_w); -#else - return posix_do_stat(self, args, "O&:stat", STAT, NULL, NULL); -#endif -} - - #ifdef HAVE_SYSTEM PyDoc_STRVAR(posix_system__doc__, "system(command) -> exit_status\n\n\ @@ -3531,22 +4183,85 @@ BOOL WINAPI Py_DeleteFileW(LPCWSTR lpFileName) #endif /* MS_WINDOWS */ PyDoc_STRVAR(posix_unlink__doc__, -"unlink(path)\n\n\ -Remove a file (same as remove(path))."); +"unlink(path, *, dir_fd=None, rmdir=False)\n\n\ +Remove a file (same as remove()).\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError.\n\ +If rmdir is True, unlink will behave like os.rmdir()."); PyDoc_STRVAR(posix_remove__doc__, -"remove(path)\n\n\ -Remove a file (same as unlink(path))."); +"remove(path, *, dir_fd=None, rmdir=False)\n\n\ +Remove a file (same as unlink()).\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError.\n\ +If rmdir is True, remove will behave like os.rmdir()."); static PyObject * -posix_unlink(PyObject *self, PyObject *args) +posix_unlink(PyObject *self, PyObject *args, PyObject *kwargs) { + path_t path; + int dir_fd = DEFAULT_DIR_FD; + int remove_dir = 0; + static char *keywords[] = {"path", "dir_fd", "rmdir", NULL}; + int result; + PyObject *return_value = NULL; + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&p:unlink", keywords, + path_converter, &path, +#ifdef HAVE_UNLINKAT + dir_fd_converter, &dir_fd, +#else + dir_fd_unavailable, &dir_fd, +#endif + &remove_dir)) + return NULL; + + Py_BEGIN_ALLOW_THREADS #ifdef MS_WINDOWS - return win32_1str(args, "remove", "y:remove", DeleteFileA, - "U:remove", Py_DeleteFileW); + if (remove_dir) { + if (path.wide) + result = RemoveDirectoryW(path.wide); + else + result = RemoveDirectoryA(path.narrow); + } + else { + if (path.wide) + result = Py_DeleteFileW(path.wide); + else + result = DeleteFileA(path.narrow); + } + result = !result; /* Windows, success=1, UNIX, success=0 */ #else - return posix_1str(args, "O&:remove", unlink); + if (remove_dir && (dir_fd == DEFAULT_DIR_FD)) + result = rmdir(path.narrow); + else +#ifdef HAVE_UNLINKAT + if (dir_fd != DEFAULT_DIR_FD) + result = unlinkat(dir_fd, path.narrow, remove_dir ? AT_REMOVEDIR : 0); + else +#endif /* HAVE_UNLINKAT */ + result = unlink(path.narrow); #endif + Py_END_ALLOW_THREADS + + if (result) { + return_value = path_error("unlink", &path); + goto exit; + } + + return_value = Py_None; + Py_INCREF(Py_None); + +exit: + path_cleanup(&path); + return return_value; } @@ -3576,6 +4291,178 @@ posix_uname(PyObject *self, PyObject *noargs) #endif /* HAVE_UNAME */ +PyDoc_STRVAR(posix_utime__doc__, +"utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)\n\ +Set the access and modified time of path.\n\ +\n\ +path may always be specified as a string.\n\ +On some platforms, path may also be specified as an open file descriptor.\n\ + If this functionality is unavailable, using it raises an exception.\n\ +\n\ +If times is not None, it must be a tuple (atime, mtime);\n\ + atime and mtime should be expressed as float seconds since the epoch.\n\ +If ns is not None, it must be a tuple (atime_ns, mtime_ns);\n\ + atime_ns and mtime_ns should be expressed as integer nanoseconds\n\ + since the epoch.\n\ +If both times and ns are None, utime uses the current time.\n\ +Specifying tuples for both times and ns is an error.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, utime will modify the symbolic link itself instead of the file the\n\ + link points to.\n\ +It is an error to use dir_fd or follow_symlinks when specifying path\n\ + as an open file descriptor.\n\ +dir_fd and follow_symlinks may not be available on your platform.\n\ + If they are unavailable, using them will raise a NotImplementedError."); + +typedef struct { + int now; + time_t atime_s; + long atime_ns; + time_t mtime_s; + long mtime_ns; +} utime_t; + +/* + * these macros assume that "utime" is a pointer to a utime_t + * they also intentionally leak the declaration of a pointer named "time" + */ +#define UTIME_TO_TIMESPEC \ + struct timespec ts[2]; \ + struct timespec *time; \ + if (utime->now) \ + time = NULL; \ + else { \ + ts[0].tv_sec = utime->atime_s; \ + ts[0].tv_nsec = utime->atime_ns; \ + ts[1].tv_sec = utime->mtime_s; \ + ts[1].tv_nsec = utime->mtime_ns; \ + time = ts; \ + } \ + +#define UTIME_TO_TIMEVAL \ + struct timeval tv[2]; \ + struct timeval *time; \ + if (utime->now) \ + time = NULL; \ + else { \ + tv[0].tv_sec = utime->atime_s; \ + tv[0].tv_usec = utime->atime_ns / 1000; \ + tv[1].tv_sec = utime->mtime_s; \ + tv[1].tv_usec = utime->mtime_ns / 1000; \ + time = tv; \ + } \ + +#define UTIME_TO_UTIMBUF \ + struct utimbuf u[2]; \ + struct utimbuf *time; \ + if (utime->now) \ + time = NULL; \ + else { \ + u.actime = utime->atime_s; \ + u.modtime = utime->mtime_s; \ + time = u; \ + } + +#define UTIME_TO_TIME_T \ + time_t timet[2]; \ + struct timet time; \ + if (utime->now) \ + time = NULL; \ + else { \ + timet[0] = utime->atime_s; \ + timet[1] = utime->mtime_s; \ + time = &timet; \ + } \ + + +#define UTIME_HAVE_DIR_FD (defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMENSAT)) + +#if UTIME_HAVE_DIR_FD + +static int +utime_dir_fd(utime_t *utime, int dir_fd, char *path, int follow_symlinks) +{ +#ifdef HAVE_UTIMENSAT + int flags = follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW; + UTIME_TO_TIMESPEC; + return utimensat(dir_fd, path, time, flags); +#elif defined(HAVE_FUTIMESAT) + UTIME_TO_TIMEVAL; + /* + * follow_symlinks will never be false here; + * we only allow !follow_symlinks and dir_fd together + * if we have utimensat() + */ + assert(follow_symlinks); + return futimesat(dir_fd, path, time); +#endif +} + +#endif + +#define UTIME_HAVE_FD (defined(HAVE_FUTIMES) || defined(HAVE_FUTIMENS)) + +#if UTIME_HAVE_FD + +static int +utime_fd(utime_t *utime, int fd) +{ +#ifdef HAVE_FUTIMENS + UTIME_TO_TIMESPEC; + return futimens(fd, time); +#else + UTIME_TO_TIMEVAL; + return futimes(fd, time); +#endif +} + +#endif + + +#define UTIME_HAVE_NOFOLLOW_SYMLINKS \ + (defined(HAVE_UTIMENSAT) || defined(HAVE_LUTIMES)) + +#if UTIME_HAVE_NOFOLLOW_SYMLINKS + +static int +utime_nofollow_symlinks(utime_t *utime, char *path) +{ +#ifdef HAVE_UTIMENSAT + UTIME_TO_TIMESPEC; + return utimensat(DEFAULT_DIR_FD, path, time, AT_SYMLINK_NOFOLLOW); +#else + UTIME_TO_TIMEVAL; + return lutimes(path, time); +#endif +} + +#endif + +#ifndef MS_WINDOWS + +static int +utime_default(utime_t *utime, char *path) +{ +#ifdef HAVE_UTIMENSAT + UTIME_TO_TIMESPEC; + return utimensat(DEFAULT_DIR_FD, path, time, 0); +#elif defined(HAVE_UTIMES) + UTIME_TO_TIMEVAL; + return utimes(path, time); +#elif defined(HAVE_UTIME_H) + UTIME_TO_UTIMBUF; + return utime(path, time); +#else + UTIME_TO_TIME_T; + return utime(path, time); +#endif +} + +#endif + static int split_py_long_to_s_and_ns(PyObject *py_long, time_t *s, long *ns) { @@ -3597,250 +4484,133 @@ exit: return result; } +static PyObject * +posix_utime(PyObject *self, PyObject *args, PyObject *kwargs) +{ + path_t path; + PyObject *times = NULL; + PyObject *ns = NULL; + int dir_fd = DEFAULT_DIR_FD; + int follow_symlinks = 1; + char *keywords[] = {"path", "times", "ns", "dir_fd", + "follow_symlinks", NULL}; -typedef int (*parameter_converter_t)(PyObject *, void *); - -typedef struct { - /* input only */ - char path_format; - parameter_converter_t converter; - char *function_name; - char *first_argument_name; - PyObject *args; - PyObject *kwargs; - - /* input/output */ - PyObject **path; - - /* output only */ - int now; - time_t atime_s; - long atime_ns; - time_t mtime_s; - long mtime_ns; -} utime_arguments; - -#define DECLARE_UA(ua, fname) \ - utime_arguments ua; \ - memset(&ua, 0, sizeof(ua)); \ - ua.function_name = fname; \ - ua.args = args; \ - ua.kwargs = kwargs; \ - ua.first_argument_name = "path"; \ - -/* UA_TO_FILETIME doesn't declare atime and mtime for you */ -#define UA_TO_FILETIME(ua, atime, mtime) \ - time_t_to_FILE_TIME(ua.atime_s, ua.atime_ns, &atime); \ - time_t_to_FILE_TIME(ua.mtime_s, ua.mtime_ns, &mtime) - -/* the rest of these macros declare the output variable for you */ -#define UA_TO_TIMESPEC(ua, ts) \ - struct timespec ts[2]; \ - ts[0].tv_sec = ua.atime_s; \ - ts[0].tv_nsec = ua.atime_ns; \ - ts[1].tv_sec = ua.mtime_s; \ - ts[1].tv_nsec = ua.mtime_ns - -#define UA_TO_TIMEVAL(ua, tv) \ - struct timeval tv[2]; \ - tv[0].tv_sec = ua.atime_s; \ - tv[0].tv_usec = ua.atime_ns / 1000; \ - tv[1].tv_sec = ua.mtime_s; \ - tv[1].tv_usec = ua.mtime_ns / 1000 - -#define UA_TO_UTIMBUF(ua, u) \ - struct utimbuf u; \ - utimbuf.actime = ua.atime_s; \ - utimbuf.modtime = ua.mtime_s - -#define UA_TO_TIME_T(ua, timet) \ - time_t timet[2]; \ - timet[0] = ua.atime_s; \ - timet[1] = ua.mtime_s + utime_t utime; +#ifdef MS_WINDOWS + HANDLE hFile; + FILETIME atime, mtime; +#else + int result; +#endif -/* - * utime_read_time_arguments() processes arguments for the utime - * family of functions. - */ + PyObject *return_value = NULL; -typedef enum { - UTIME_SUCCESS = 0, - UTIME_PARSE_FAILURE = 1, - UTIME_TIMES_AND_NS_COLLISION = 2, - UTIME_TIMES_CONVERSION_FAILURE = 3, - UTIME_NS_CONVERSION_FAILURE = 4, -} utime_status; + memset(&path, 0, sizeof(path)); +#if UTIME_HAVE_FD + path.allow_fd = 1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "O&|O$OO&p:utime", keywords, + path_converter, &path, + ×, &ns, +#if UTIME_HAVE_DIR_FD + dir_fd_converter, &dir_fd, +#else + dir_fd_unavailable, &dir_fd, +#endif + &follow_symlinks + )) + return NULL; -static utime_status -utime_read_time_arguments(utime_arguments *ua) -{ - PyObject *times = NULL; - PyObject *ns = NULL; - char format[24]; - char *kwlist[4]; - char **kw = kwlist; - utime_status return_value; - int parse_result; - - *kw++ = ua->first_argument_name; - *kw++ = "times"; - *kw++ = "ns"; - *kw = NULL; - - sprintf(format, "%c%s|O$O:%s", - ua->path_format, - ua->converter ? "&" : "", - ua->function_name); - - if (ua->converter) - parse_result = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs, - format, kwlist, ua->converter, ua->path, ×, &ns); - else - parse_result = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs, - format, kwlist, ua->path, ×, &ns); - - if (!parse_result) - return UTIME_PARSE_FAILURE; - - if (times && ns) { - PyErr_Format(PyExc_RuntimeError, - "%s: you may specify either 'times'" - " or 'ns' but not both", - ua->function_name); - return_value = UTIME_TIMES_AND_NS_COLLISION; - goto fail; + if (times && (times != Py_None) && ns) { + PyErr_SetString(PyExc_ValueError, + "utime: you may specify either 'times'" + " or 'ns' but not both"); + goto exit; } if (times && (times != Py_None)) { if (!PyTuple_CheckExact(times) || (PyTuple_Size(times) != 2)) { - PyErr_Format(PyExc_TypeError, - "%s: 'times' must be either" - " a tuple of two ints or None", - ua->function_name); - return_value = UTIME_TIMES_CONVERSION_FAILURE; - goto fail; + PyErr_SetString(PyExc_TypeError, + "utime: 'times' must be either" + " a tuple of two ints or None"); + goto exit; } - ua->now = 0; + utime.now = 0; if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0), - &ua->atime_s, &ua->atime_ns) == -1 || + &utime.atime_s, &utime.atime_ns) == -1 || _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1), - &ua->mtime_s, &ua->mtime_ns) == -1) { - return_value = UTIME_TIMES_CONVERSION_FAILURE; - goto fail; + &utime.mtime_s, &utime.mtime_ns) == -1) { + goto exit; } - return UTIME_SUCCESS; } - - if (ns) { + else if (ns) { if (!PyTuple_CheckExact(ns) || (PyTuple_Size(ns) != 2)) { - PyErr_Format(PyExc_TypeError, - "%s: 'ns' must be a tuple of two ints", - ua->function_name); - return_value = UTIME_NS_CONVERSION_FAILURE; - goto fail; + PyErr_SetString(PyExc_TypeError, + "utime: 'ns' must be a tuple of two ints"); + goto exit; } - ua->now = 0; + utime.now = 0; if (!split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 0), - &ua->atime_s, &ua->atime_ns) || + &utime.atime_s, &utime.atime_ns) || !split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 1), - &ua->mtime_s, &ua->mtime_ns)) { - return_value = UTIME_NS_CONVERSION_FAILURE; - goto fail; + &utime.mtime_s, &utime.mtime_ns)) { + goto exit; } - return UTIME_SUCCESS; + } + else { + /* times and ns are both None/unspecified. use "now". */ + utime.now = 1; } - /* either times=None, or neither times nor ns was specified. use "now". */ - ua->now = 1; - return UTIME_SUCCESS; - - fail: - if (ua->converter) - Py_DECREF(*ua->path); - return return_value; -} +#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; -PyDoc_STRVAR(posix_utime__doc__, -"utime(path[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\ -Set the access and modified time of the file.\n\ -If the second argument ('times') is specified,\n\ - the values should be expressed as float seconds since the epoch.\n\ -If the keyword argument 'ns' is specified,\n\ - the values should be expressed as integer nanoseconds since the epoch.\n\ -If neither the second nor the 'ns' argument is specified,\n\ - utime uses the current time.\n\ -Specifying both 'times' and 'ns' is an error."); +#if !defined(HAVE_UTIMENSAT) + if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { + PyErr_SetString(PyExc_RuntimeError, + "utime: cannot use dir_fd and follow_symlinks " + "together on this platform"); + goto exit; + } +#endif -static PyObject * -posix_utime(PyObject *self, PyObject *args, PyObject *kwargs) -{ #ifdef MS_WINDOWS - PyObject *upath; - HANDLE hFile; - PyObject *result = NULL; - FILETIME atime, mtime; - - DECLARE_UA(ua, "utime"); - - ua.path_format = 'U'; - ua.path = &upath; - - switch (utime_read_time_arguments(&ua)) { - default: - return NULL; - case UTIME_SUCCESS: { - wchar_t *wpath = PyUnicode_AsUnicode(upath); - if (wpath == NULL) - return NULL; - Py_BEGIN_ALLOW_THREADS - hFile = CreateFileW(wpath, FILE_WRITE_ATTRIBUTES, 0, + Py_BEGIN_ALLOW_THREADS + if (path.wide) + hFile = CreateFileW(path.wide, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - Py_END_ALLOW_THREADS - if (hFile == INVALID_HANDLE_VALUE) - return win32_error_object("utime", upath); - break; - } - case UTIME_PARSE_FAILURE: { - const char *apath; - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); - - ua.path_format = 'y'; - ua.path = (PyObject **)&apath; - if (utime_read_time_arguments(&ua) != UTIME_SUCCESS) - return NULL; - if (win32_warn_bytes_api()) - return NULL; - - Py_BEGIN_ALLOW_THREADS - hFile = CreateFileA(apath, FILE_WRITE_ATTRIBUTES, 0, + else + hFile = CreateFileA(path.narrow, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - Py_END_ALLOW_THREADS - if (hFile == INVALID_HANDLE_VALUE) { - win32_error("utime", apath); - return NULL; - } - break; - } - + Py_END_ALLOW_THREADS + if (hFile == INVALID_HANDLE_VALUE) { + win32_error_object("utime", path.object); + goto exit; } - if (ua.now) { + if (utime.now) { SYSTEMTIME now; GetSystemTime(&now); if (!SystemTimeToFileTime(&now, &mtime) || !SystemTimeToFileTime(&now, &atime)) { win32_error("utime", NULL); - goto done; + goto exit; } } else { - UA_TO_FILETIME(ua, atime, mtime); + time_t_to_FILE_TIME(utime.atime_s, utime.atime_ns, &atime); + time_t_to_FILE_TIME(utime.mtime_s, utime.mtime_ns, &mtime); } if (!SetFileTime(hFile, NULL, &atime, &mtime)) { /* Avoid putting the file name into the error here, @@ -3848,151 +4618,52 @@ posix_utime(PyObject *self, PyObject *args, PyObject *kwargs) something is wrong with the file, when it also could be the time stamp that gives a problem. */ win32_error("utime", NULL); - goto done; + goto exit; } - Py_INCREF(Py_None); - result = Py_None; -done: - CloseHandle(hFile); - return result; #else /* MS_WINDOWS */ - PyObject *opath; - char *path; - int res; - - DECLARE_UA(ua, "utime"); - - ua.path_format = 'O'; - ua.path = &opath; - ua.converter = PyUnicode_FSConverter; + Py_BEGIN_ALLOW_THREADS - if (utime_read_time_arguments(&ua) != UTIME_SUCCESS) - return NULL; - path = PyBytes_AsString(opath); - if (ua.now) { - Py_BEGIN_ALLOW_THREADS - res = utime(path, NULL); - Py_END_ALLOW_THREADS - } - else { - Py_BEGIN_ALLOW_THREADS -#ifdef HAVE_UTIMENSAT - UA_TO_TIMESPEC(ua, buf); - res = utimensat(AT_FDCWD, path, buf, 0); -#elif defined(HAVE_UTIMES) - UA_TO_TIMEVAL(ua, buf); - res = utimes(path, buf); -#elif defined(HAVE_UTIME_H) - /* XXX should define struct utimbuf instead, above */ - UA_TO_UTIMBUF(ua, buf); - res = utime(path, &buf); -#else - UA_TO_TIME_T(ua, buf); - res = utime(path, buf); +#if UTIME_HAVE_NOFOLLOW_SYMLINKS + if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) + result = utime_nofollow_symlinks(&utime, path.narrow); + else #endif - Py_END_ALLOW_THREADS - } - - if (res < 0) { - return posix_error_with_allocated_filename(opath); - } - Py_DECREF(opath); - Py_RETURN_NONE; -#undef UTIME_EXTRACT -#endif /* MS_WINDOWS */ -} -#ifdef HAVE_FUTIMES -PyDoc_STRVAR(posix_futimes__doc__, -"futimes(fd[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\ -Set the access and modified time of the file specified by the file\n\ -descriptor fd. See utime for the semantics of the times and ns parameters."); - -static PyObject * -posix_futimes(PyObject *self, PyObject *args, PyObject *kwargs) -{ - int res, fd; - - DECLARE_UA(ua, "futimes"); - - ua.path_format = 'i'; - ua.path = (PyObject **)&fd; - ua.first_argument_name = "fd"; - - if (utime_read_time_arguments(&ua) != UTIME_SUCCESS) - return NULL; - - if (ua.now) { - Py_BEGIN_ALLOW_THREADS - res = futimes(fd, NULL); - Py_END_ALLOW_THREADS - } - else { - Py_BEGIN_ALLOW_THREADS - { -#ifdef HAVE_FUTIMENS - UA_TO_TIMESPEC(ua, buf); - res = futimens(fd, buf); -#else - UA_TO_TIMEVAL(ua, buf); - res = futimes(fd, buf); +#if UTIME_HAVE_DIR_FD + if ((dir_fd != DEFAULT_DIR_FD) || (!follow_symlinks)) + result = utime_dir_fd(&utime, dir_fd, path.narrow, follow_symlinks); + else #endif - } - Py_END_ALLOW_THREADS - } - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} + +#if UTIME_HAVE_FD + if (path.fd != -1) + result = utime_fd(&utime, path.fd); + else #endif -#ifdef HAVE_LUTIMES -PyDoc_STRVAR(posix_lutimes__doc__, -"lutimes(path[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\ -Like utime(), but if path is a symbolic link, it is not dereferenced."); + result = utime_default(&utime, path.narrow); -static PyObject * -posix_lutimes(PyObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *opath; - const char *path; - int res; + Py_END_ALLOW_THREADS - DECLARE_UA(ua, "lutimes"); + if (result < 0) { + /* see previous comment about not putting filename in error here */ + return_value = posix_error(); + goto exit; + } - ua.path_format = 'O'; - ua.path = &opath; - ua.converter = PyUnicode_FSConverter; +#endif /* MS_WINDOWS */ - if (utime_read_time_arguments(&ua) != UTIME_SUCCESS) - return NULL; - path = PyBytes_AsString(opath); + Py_INCREF(Py_None); + return_value = Py_None; - if (ua.now) { - /* optional time values not given */ - Py_BEGIN_ALLOW_THREADS - res = lutimes(path, NULL); - Py_END_ALLOW_THREADS - } - else { - Py_BEGIN_ALLOW_THREADS - { -#ifdef HAVE_UTIMENSAT - UA_TO_TIMESPEC(ua, buf); - res = utimensat(AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); -#else - UA_TO_TIMEVAL(ua, buf); - res = lutimes(path, buf); +exit: + path_cleanup(&path); +#ifdef MS_WINDOWS + if (hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); #endif - } - Py_END_ALLOW_THREADS - } - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; + return return_value; } -#endif /* Process operations */ @@ -4206,121 +4877,79 @@ Execute a path with arguments and environment, replacing current process.\n\ \n\ path: path of executable file\n\ args: tuple or list of arguments\n\ - env: dictionary of strings mapping to strings"); + env: dictionary of strings mapping to strings\n\ +\n\ +On some platforms, you may specify an open file descriptor for path;\n\ + execve will execute the program the file descriptor is open to.\n\ + If this functionality is unavailable, using it raises NotImplementedError."); static PyObject * -posix_execve(PyObject *self, PyObject *args) +posix_execve(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *opath; - char *path; + path_t path; PyObject *argv, *env; - char **argvlist; + char **argvlist = NULL; char **envlist; Py_ssize_t argc, envc; + static char *keywords[] = {"path", "argv", "environment", NULL}; /* execve has three arguments: (path, argv, env), where argv is a list or tuple of strings and env is a dictionary like posix.environ. */ - if (!PyArg_ParseTuple(args, "O&OO:execve", - PyUnicode_FSConverter, - &opath, &argv, &env)) + memset(&path, 0, sizeof(path)); +#ifdef HAVE_FEXECVE + path.allow_fd = 1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&OO:execve", keywords, + path_converter, &path, + &argv, &env + )) return NULL; - path = PyBytes_AsString(opath); + if (!PyList_Check(argv) && !PyTuple_Check(argv)) { PyErr_SetString(PyExc_TypeError, - "execve() arg 2 must be a tuple or list"); - goto fail_0; + "execve: argv must be a tuple or list"); + goto fail; } argc = PySequence_Size(argv); if (!PyMapping_Check(env)) { PyErr_SetString(PyExc_TypeError, - "execve() arg 3 must be a mapping object"); - goto fail_0; + "execve: environment must be a mapping object"); + goto fail; } argvlist = parse_arglist(argv, &argc); if (argvlist == NULL) { - goto fail_0; - } - - envlist = parse_envlist(env, &envc); - if (envlist == NULL) - goto fail_1; - - execve(path, argvlist, envlist); - - /* If we get here it's definitely an error */ - - (void) posix_error(); - - while (--envc >= 0) - PyMem_DEL(envlist[envc]); - PyMem_DEL(envlist); - fail_1: - free_string_array(argvlist, argc); - fail_0: - Py_DECREF(opath); - return NULL; -} -#endif /* HAVE_EXECV */ - -#ifdef HAVE_FEXECVE -PyDoc_STRVAR(posix_fexecve__doc__, -"fexecve(fd, args, env)\n\n\ -Execute the program specified by a file descriptor with arguments and\n\ -environment, replacing the current process.\n\ -\n\ - fd: file descriptor of executable\n\ - args: tuple or list of arguments\n\ - env: dictionary of strings mapping to strings"); - -static PyObject * -posix_fexecve(PyObject *self, PyObject *args) -{ - int fd; - PyObject *argv, *env; - char **argvlist; - char **envlist; - Py_ssize_t argc, envc; - - if (!PyArg_ParseTuple(args, "iOO:fexecve", - &fd, &argv, &env)) - return NULL; - if (!PyList_Check(argv) && !PyTuple_Check(argv)) { - PyErr_SetString(PyExc_TypeError, - "fexecve() arg 2 must be a tuple or list"); - return NULL; - } - argc = PySequence_Size(argv); - if (!PyMapping_Check(env)) { - PyErr_SetString(PyExc_TypeError, - "fexecve() arg 3 must be a mapping object"); - return NULL; + goto fail; } - argvlist = parse_arglist(argv, &argc); - if (argvlist == NULL) - return NULL; - envlist = parse_envlist(env, &envc); if (envlist == NULL) goto fail; - fexecve(fd, argvlist, envlist); +#ifdef HAVE_FEXECVE + if (path.fd > -1) + fexecve(path.fd, argvlist, envlist); + else +#endif + execve(path.narrow, argvlist, envlist); /* If we get here it's definitely an error */ - (void) posix_error(); + path_posix_error("execve", &path); while (--envc >= 0) PyMem_DEL(envlist[envc]); PyMem_DEL(envlist); fail: - free_string_array(argvlist, argc); + if (argvlist) + free_string_array(argvlist, argc); + path_cleanup(&path); return NULL; } -#endif /* HAVE_FEXECVE */ +#endif /* HAVE_EXECV */ + #ifdef HAVE_SPAWNV PyDoc_STRVAR(posix_spawnv__doc__, @@ -6433,107 +7062,224 @@ posix_wait(PyObject *self, PyObject *noargs) #endif -PyDoc_STRVAR(posix_lstat__doc__, -"lstat(path) -> stat result\n\n\ -Like stat(path), but do not follow symbolic links."); +#if defined(HAVE_READLINK) || defined(MS_WINDOWS) +PyDoc_STRVAR(readlink__doc__, +"readlink(path, *, dir_fd=None) -> path\n\n\ +Return a string representing the path to which the symbolic link points.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError."); +#endif + +#ifdef HAVE_READLINK static PyObject * -posix_lstat(PyObject *self, PyObject *args) +posix_readlink(PyObject *self, PyObject *args, PyObject *kwargs) { -#ifdef HAVE_LSTAT - return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL); -#else /* !HAVE_LSTAT */ -#ifdef MS_WINDOWS - return posix_do_stat(self, args, "O&:lstat", win32_lstat, "U:lstat", - win32_lstat_w); + path_t path; + int dir_fd = DEFAULT_DIR_FD; + char buffer[MAXPATHLEN]; + ssize_t length; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "dir_fd", NULL}; + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:readlink", keywords, + path_converter, &path, +#ifdef HAVE_READLINKAT + dir_fd_converter, &dir_fd #else - return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL); + dir_fd_unavailable, &dir_fd +#endif + )) + return NULL; + + Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_READLINKAT + if (dir_fd != DEFAULT_DIR_FD) + length = readlinkat(dir_fd, path.narrow, buffer, sizeof(buffer)); + else #endif -#endif /* !HAVE_LSTAT */ + length = readlink(path.narrow, buffer, sizeof(buffer)); + Py_END_ALLOW_THREADS + + if (length < 0) { + return_value = path_posix_error("readlink", &path); + goto exit; + } + + if (PyUnicode_Check(path.object)) + return_value = PyUnicode_DecodeFSDefaultAndSize(buffer, length); + else + return_value = PyBytes_FromStringAndSize(buffer, length); +exit: + path_cleanup(&path); + return return_value; } -#ifdef HAVE_READLINK -PyDoc_STRVAR(posix_readlink__doc__, -"readlink(path) -> path\n\n\ -Return a string representing the path to which the symbolic link points."); +#endif /* HAVE_READLINK */ + + +#ifdef HAVE_SYMLINK +PyDoc_STRVAR(posix_symlink__doc__, +"symlink(src, dst, target_is_directory=False, *, dir_fd=None)\n\n\ +Create a symbolic link pointing to src named dst.\n\n\ +target_is_directory is required on Windows if the target is to be\n\ + interpreted as a directory. (On Windows, symlink requires\n\ + Windows 6.0 or greater, and raises a NotImplementedError otherwise.)\n\ + target_is_directory is ignored on non-Windows platforms.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError."); + +#if defined(MS_WINDOWS) + +/* Grab CreateSymbolicLinkW dynamically from kernel32 */ +static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPWSTR, LPWSTR, DWORD) = NULL; +static DWORD (CALLBACK *Py_CreateSymbolicLinkA)(LPSTR, LPSTR, DWORD) = NULL; +static int +check_CreateSymbolicLink() +{ + HINSTANCE hKernel32; + /* only recheck */ + if (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA) + return 1; + hKernel32 = GetModuleHandleW(L"KERNEL32"); + *(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32, + "CreateSymbolicLinkW"); + *(FARPROC*)&Py_CreateSymbolicLinkA = GetProcAddress(hKernel32, + "CreateSymbolicLinkA"); + return (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA); +} + +#endif static PyObject * -posix_readlink(PyObject *self, PyObject *args) +posix_symlink(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject* v; - char buf[MAXPATHLEN]; - PyObject *opath; - char *path; - int n; - int arg_is_unicode = 0; + path_t src; + path_t dst; + int dir_fd = DEFAULT_DIR_FD; + int target_is_directory = 0; + static char *keywords[] = {"src", "dst", "target_is_directory", + "dir_fd", NULL}; + PyObject *return_value; +#ifdef MS_WINDOWS + DWORD result; +#else + int result; +#endif - if (!PyArg_ParseTuple(args, "O&:readlink", - PyUnicode_FSConverter, &opath)) - return NULL; - path = PyBytes_AsString(opath); - v = PySequence_GetItem(args, 0); - if (v == NULL) { - Py_DECREF(opath); + memset(&src, 0, sizeof(src)); + src.argument_name = "src"; + memset(&dst, 0, sizeof(dst)); + dst.argument_name = "dst"; + +#ifdef MS_WINDOWS + if (!check_CreateSymbolicLink()) { + PyErr_SetString(PyExc_NotImplementedError, + "CreateSymbolicLink functions not found"); + return NULL; + } + if (!win32_can_symlink) { + PyErr_SetString(PyExc_OSError, "symbolic link privilege not held"); + return NULL; + } +#endif + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|i$O&:symlink", + keywords, + path_converter, &src, + path_converter, &dst, + &target_is_directory, +#ifdef HAVE_SYMLINKAT + dir_fd_converter, &dir_fd +#else + dir_fd_unavailable, &dir_fd +#endif + )) return NULL; - } - if (PyUnicode_Check(v)) { - arg_is_unicode = 1; + if ((src.narrow && dst.wide) || (src.wide && dst.narrow)) { + PyErr_SetString(PyExc_ValueError, + "symlink: src and dst must be the same type"); + return_value = NULL; + goto exit; } - Py_DECREF(v); +#ifdef MS_WINDOWS Py_BEGIN_ALLOW_THREADS - n = readlink(path, buf, (int) sizeof buf); + if (dst.wide) + result = Py_CreateSymbolicLinkW(dst.wide, src.wide, + target_is_directory); + else + result = Py_CreateSymbolicLinkA(dst.narrow, src.narrow, + target_is_directory); Py_END_ALLOW_THREADS - if (n < 0) - return posix_error_with_allocated_filename(opath); - Py_DECREF(opath); - if (arg_is_unicode) - return PyUnicode_DecodeFSDefaultAndSize(buf, n); - else - return PyBytes_FromStringAndSize(buf, n); -} -#endif /* HAVE_READLINK */ + if (!result) { + return_value = win32_error_object("symlink", src.object); + goto exit; + } +#else -#if defined(HAVE_SYMLINK) && !defined(MS_WINDOWS) -PyDoc_STRVAR(posix_symlink__doc__, -"symlink(src, dst)\n\n\ -Create a symbolic link pointing to src named dst."); + Py_BEGIN_ALLOW_THREADS +#if HAVE_SYMLINKAT + if (dir_fd != DEFAULT_DIR_FD) + result = symlinkat(src.narrow, dir_fd, dst.narrow); + else +#endif + result = symlink(src.narrow, dst.narrow); + Py_END_ALLOW_THREADS -static PyObject * -posix_symlink(PyObject *self, PyObject *args) -{ - return posix_2str(args, "O&O&:symlink", symlink); + if (result) { + return_value = path_error("symlink", &dst); + goto exit; + } +#endif + + return_value = Py_None; + Py_INCREF(Py_None); + goto exit; /* silence "unused label" warning */ +exit: + path_cleanup(&src); + path_cleanup(&dst); + return return_value; } + #endif /* HAVE_SYMLINK */ -#if !defined(HAVE_READLINK) && defined(MS_WINDOWS) -PyDoc_STRVAR(win_readlink__doc__, -"readlink(path) -> path\n\n\ -Return a string representing the path to which the symbolic link points."); +#if !defined(HAVE_READLINK) && defined(MS_WINDOWS) -/* Windows readlink implementation */ static PyObject * -win_readlink(PyObject *self, PyObject *args) +win_readlink(PyObject *self, PyObject *args, PyObject *kwargs) { wchar_t *path; DWORD n_bytes_returned; DWORD io_result; PyObject *po, *result; + int dir_fd; HANDLE reparse_point_handle; char target_buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)target_buffer; wchar_t *print_name; - if (!PyArg_ParseTuple(args, - "U:readlink", - &po)) + static char *keywords[] = {"path", "dir_fd", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "U|$O&:readlink", keywords, + &po, + dir_fd_unavailable, &dir_fd + )) return NULL; + path = PyUnicode_AsUnicode(po); if (path == NULL) return NULL; @@ -6585,90 +7331,6 @@ win_readlink(PyObject *self, PyObject *args) #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */ -#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) - -/* Grab CreateSymbolicLinkW dynamically from kernel32 */ -static int has_CreateSymbolicLinkW = 0; -static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPWSTR, LPWSTR, DWORD); -static int -check_CreateSymbolicLinkW() -{ - HINSTANCE hKernel32; - /* only recheck */ - if (has_CreateSymbolicLinkW) - return has_CreateSymbolicLinkW; - hKernel32 = GetModuleHandleW(L"KERNEL32"); - *(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32, - "CreateSymbolicLinkW"); - if (Py_CreateSymbolicLinkW) - has_CreateSymbolicLinkW = 1; - return has_CreateSymbolicLinkW; -} - -PyDoc_STRVAR(win_symlink__doc__, -"symlink(src, dst, target_is_directory=False)\n\n\ -Create a symbolic link pointing to src named dst.\n\ -target_is_directory is required if the target is to be interpreted as\n\ -a directory.\n\ -This function requires Windows 6.0 or greater, and raises a\n\ -NotImplementedError otherwise."); - -static PyObject * -win_symlink(PyObject *self, PyObject *args, PyObject *kwargs) -{ - static char *kwlist[] = {"src", "dest", "target_is_directory", NULL}; - PyObject *osrc, *odest; - PyObject *usrc = NULL, *udest = NULL; - wchar_t *wsrc, *wdest; - int target_is_directory = 0; - DWORD res; - - if (!check_CreateSymbolicLinkW()) - { - /* raise NotImplementedError */ - return PyErr_Format(PyExc_NotImplementedError, - "CreateSymbolicLinkW not found"); - } - if (!PyArg_ParseTupleAndKeywords( - args, kwargs, "OO|i:symlink", kwlist, - &osrc, &odest, &target_is_directory)) - return NULL; - - usrc = win32_decode_filename(osrc); - if (!usrc) - return NULL; - udest = win32_decode_filename(odest); - if (!udest) - goto error; - - if (win32_can_symlink == 0) - return PyErr_Format(PyExc_OSError, "symbolic link privilege not held"); - - wsrc = PyUnicode_AsUnicode(usrc); - if (wsrc == NULL) - goto error; - wdest = PyUnicode_AsUnicode(udest); - if (wsrc == NULL) - goto error; - - Py_BEGIN_ALLOW_THREADS - res = Py_CreateSymbolicLinkW(wdest, wsrc, target_is_directory); - Py_END_ALLOW_THREADS - - Py_DECREF(usrc); - Py_DECREF(udest); - if (!res) - return win32_error_object("symlink", osrc); - - Py_INCREF(Py_None); - return Py_None; - -error: - Py_XDECREF(usrc); - Py_XDECREF(udest); - return NULL; -} -#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */ #ifdef HAVE_TIMES #if defined(PYCC_VACPP) && defined(PYOS_OS2) @@ -6848,57 +7510,68 @@ posix_tcsetpgrp(PyObject *self, PyObject *args) /* Functions acting on file descriptors */ PyDoc_STRVAR(posix_open__doc__, -"open(filename, flag [, mode=0777]) -> fd\n\n\ -Open a file (for low level IO)."); +"open(path, flags, mode=0o777, *, dir_fd=None)\n\n\ +Open a file for low level IO. Returns a file handle (integer).\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError."); static PyObject * -posix_open(PyObject *self, PyObject *args) +posix_open(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *ofile; - char *file; - int flag; + path_t path; + int flags; int mode = 0777; + int dir_fd = DEFAULT_DIR_FD; int fd; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "flags", "mode", "dir_fd", NULL}; -#ifdef MS_WINDOWS - PyObject *po; - if (PyArg_ParseTuple(args, "Ui|i:open", &po, &flag, &mode)) { - wchar_t *wpath = PyUnicode_AsUnicode(po); - if (wpath == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - fd = _wopen(wpath, flag, mode); - Py_END_ALLOW_THREADS - if (fd < 0) - return posix_error(); - return PyLong_FromLong((long)fd); - } - /* Drop the argument parsing error as narrow strings - are also valid. */ - PyErr_Clear(); + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&i|i$O&:open", keywords, + path_converter, &path, + &flags, &mode, +#ifdef HAVE_OPENAT + dir_fd_converter, &dir_fd +#else + dir_fd_unavailable, &dir_fd #endif - - if (!PyArg_ParseTuple(args, "O&i|i:open", - PyUnicode_FSConverter, &ofile, - &flag, &mode)) + )) return NULL; + + Py_BEGIN_ALLOW_THREADS #ifdef MS_WINDOWS - if (win32_warn_bytes_api()) { - Py_DECREF(ofile); - return NULL; - } + if (path.wide) + fd = _wopen(path.wide, flags, mode); + else #endif - file = PyBytes_AsString(ofile); - Py_BEGIN_ALLOW_THREADS - fd = open(file, flag, mode); +#ifdef HAVE_OPENAT + if (dir_fd != DEFAULT_DIR_FD) + fd = openat(dir_fd, path.narrow, flags, mode); + else +#endif + fd = open(path.narrow, flags, mode); Py_END_ALLOW_THREADS - if (fd < 0) - return posix_error_with_allocated_filename(ofile); - Py_DECREF(ofile); - return PyLong_FromLong((long)fd); -} + if (fd == -1) { +#ifdef MS_WINDOWS + /* force use of posix_error here for exact backwards compatibility */ + if (path.wide) + return_value = posix_error(); + else +#endif + return_value = path_error("open", &path); + goto exit; + } + + return_value = PyLong_FromLong((long)fd); + +exit: + path_cleanup(&path); + return return_value; +} PyDoc_STRVAR(posix_close__doc__, "close(fd)\n\n\ @@ -7407,7 +8080,8 @@ done: PyDoc_STRVAR(posix_fstat__doc__, "fstat(fd) -> stat result\n\n\ -Like stat(), but for an open file descriptor."); +Like stat(), but for an open file descriptor.\n\ +Equivalent to stat(fd=fd)."); static PyObject * posix_fstat(PyObject *self, PyObject *args) @@ -7589,63 +8263,118 @@ posix_pwrite(PyObject *self, PyObject *args) #ifdef HAVE_MKFIFO PyDoc_STRVAR(posix_mkfifo__doc__, -"mkfifo(filename [, mode=0666])\n\n\ -Create a FIFO (a POSIX named pipe)."); +"mkfifo(path, mode=0o666, *, dir_fd=None)\n\n\ +Create a FIFO (a POSIX named pipe).\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError."); static PyObject * -posix_mkfifo(PyObject *self, PyObject *args) +posix_mkfifo(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *opath; - char *filename; + path_t path; int mode = 0666; - int res; - if (!PyArg_ParseTuple(args, "O&|i:mkfifo", PyUnicode_FSConverter, &opath, - &mode)) + int dir_fd = DEFAULT_DIR_FD; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "mode", "dir_fd", NULL}; + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|i$O&:mkfifo", keywords, + path_converter, &path, + &mode, +#ifdef HAVE_MKFIFOAT + dir_fd_converter, &dir_fd +#else + dir_fd_unavailable, &dir_fd +#endif + )) return NULL; - filename = PyBytes_AS_STRING(opath); + Py_BEGIN_ALLOW_THREADS - res = mkfifo(filename, mode); +#ifdef HAVE_MKFIFOAT + if (dir_fd != DEFAULT_DIR_FD) + result = mkfifoat(dir_fd, path.narrow, mode); + else +#endif + result = mkfifo(path.narrow, mode); Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); + + if (result < 0) { + return_value = posix_error(); + goto exit; + } + + return_value = Py_None; Py_INCREF(Py_None); - return Py_None; + +exit: + path_cleanup(&path); + return return_value; } #endif - #if defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV) PyDoc_STRVAR(posix_mknod__doc__, -"mknod(filename [, mode=0600, device])\n\n\ +"mknod(filename, mode=0o600, device=0, *, dir_fd=None)\n\n\ Create a filesystem node (file, device special file or named pipe)\n\ named filename. mode specifies both the permissions to use and the\n\ type of node to be created, being combined (bitwise OR) with one of\n\ S_IFREG, S_IFCHR, S_IFBLK, and S_IFIFO. For S_IFCHR and S_IFBLK,\n\ device defines the newly created device special file (probably using\n\ -os.makedev()), otherwise it is ignored."); +os.makedev()), otherwise it is ignored.\n\ +\n\ +If dir_fd is not None, it should be a file descriptor open to a directory,\n\ + and path should be relative; path will then be relative to that directory.\n\ +dir_fd may not be implemented on your platform.\n\ + If it is unavailable, using it will raise a NotImplementedError."); static PyObject * -posix_mknod(PyObject *self, PyObject *args) +posix_mknod(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *opath; - char *filename; - int mode = 0600; + path_t path; + int mode = 0666; int device = 0; - int res; - if (!PyArg_ParseTuple(args, "O&|ii:mknod", PyUnicode_FSConverter, &opath, - &mode, &device)) + int dir_fd = DEFAULT_DIR_FD; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "mode", "device", "dir_fd", NULL}; + + memset(&path, 0, sizeof(path)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|ii$O&:mknod", keywords, + path_converter, &path, + &mode, &device, +#ifdef HAVE_MKNODAT + dir_fd_converter, &dir_fd +#else + dir_fd_unavailable, &dir_fd +#endif + )) return NULL; - filename = PyBytes_AS_STRING(opath); + Py_BEGIN_ALLOW_THREADS - res = mknod(filename, mode, device); +#ifdef HAVE_MKNODAT + if (dir_fd != DEFAULT_DIR_FD) + result = mknodat(dir_fd, path.narrow, mode, device); + else +#endif + result = mknod(path.narrow, mode, device); Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); + + if (result < 0) { + return_value = posix_error(); + goto exit; + } + + return_value = Py_None; Py_INCREF(Py_None); - return Py_None; + +exit: + path_cleanup(&path); + return return_value; } #endif @@ -8163,7 +8892,8 @@ _pystatvfs_fromstructstatvfs(struct statvfs st) { PyDoc_STRVAR(posix_fstatvfs__doc__, "fstatvfs(fd) -> statvfs result\n\n\ -Perform an fstatvfs system call on the given fd."); +Perform an fstatvfs system call on the given fd.\n\ +Equivalent to statvfs(fd)."); static PyObject * posix_fstatvfs(PyObject *self, PyObject *args) @@ -8188,28 +8918,58 @@ posix_fstatvfs(PyObject *self, PyObject *args) #include <sys/statvfs.h> PyDoc_STRVAR(posix_statvfs__doc__, -"statvfs(path) -> statvfs result\n\n\ -Perform a statvfs system call on the given path."); +"statvfs(path)\n\n\ +Perform a statvfs system call on the given path.\n\ +\n\ +path may always be specified as a string.\n\ +On some platforms, path may also be specified as an open file descriptor.\n\ + If this functionality is unavailable, using it raises an exception."); static PyObject * -posix_statvfs(PyObject *self, PyObject *args) +posix_statvfs(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *path; - int res; + static char *keywords[] = {"path", NULL}; + path_t path; + int result; + PyObject *return_value = NULL; struct statvfs st; - if (!PyArg_ParseTuple(args, "O&:statvfs", PyUnicode_FSConverter, &path)) + + memset(&path, 0, sizeof(path)); +#ifdef HAVE_FSTATVFS + path.allow_fd = 1; +#endif + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&:statvfs", keywords, + path_converter, &path + )) return NULL; + Py_BEGIN_ALLOW_THREADS - res = statvfs(PyBytes_AS_STRING(path), &st); +#ifdef HAVE_FSTATVFS + if (path.fd != -1) { +#ifdef __APPLE__ + /* handle weak-linking on Mac OS X 10.3 */ + if (fstatvfs == NULL) { + fd_specified("statvfs", path.fd); + goto exit; + } +#endif + result = fstatvfs(path.fd, &st); + } + else +#endif + result = statvfs(path.narrow, &st); Py_END_ALLOW_THREADS - if (res != 0) { - posix_error_with_filename(PyBytes_AS_STRING(path)); - Py_DECREF(path); - return NULL; + + if (result) { + return_value = path_posix_error("statvfs", &path); + goto exit; } - Py_DECREF(path); - return _pystatvfs_fromstructstatvfs(st); + return_value = _pystatvfs_fromstructstatvfs(st); + +exit: + path_cleanup(&path); + return return_value; } #endif /* HAVE_STATVFS */ @@ -9517,946 +10277,301 @@ posix_getresgid (PyObject *self, PyObject *noargs) } #endif -/* Posix *at family of functions: - faccessat, fchmodat, fchownat, fstatat, futimesat, - linkat, mkdirat, mknodat, openat, readlinkat, renameat, symlinkat, - unlinkat, utimensat, mkfifoat */ - -#ifdef HAVE_FACCESSAT -PyDoc_STRVAR(posix_faccessat__doc__, -"faccessat(dirfd, path, mode, flags=0) -> True if granted, False otherwise\n\n\ -Like access() but if path is relative, it is taken as relative to dirfd.\n\ -flags is optional and can be constructed by ORing together zero or more\n\ -of these values: AT_SYMLINK_NOFOLLOW, AT_EACCESS.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_faccessat(PyObject *self, PyObject *args) -{ - PyObject *opath; - char *path; - int mode; - int res; - int dirfd, flags = 0; - if (!PyArg_ParseTuple(args, "iO&i|i:faccessat", - &dirfd, PyUnicode_FSConverter, &opath, &mode, &flags)) - return NULL; - path = PyBytes_AsString(opath); - Py_BEGIN_ALLOW_THREADS - res = faccessat(dirfd, path, mode, flags); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - return PyBool_FromLong(res == 0); -} -#endif - -#ifdef HAVE_FCHMODAT -PyDoc_STRVAR(posix_fchmodat__doc__, -"fchmodat(dirfd, path, mode, flags=0)\n\n\ -Like chmod() but if path is relative, it is taken as relative to dirfd.\n\ -flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_fchmodat(PyObject *self, PyObject *args) -{ - int dirfd, mode, res; - int flags = 0; - PyObject *opath; - char *path; - - if (!PyArg_ParseTuple(args, "iO&i|i:fchmodat", - &dirfd, PyUnicode_FSConverter, &opath, &mode, &flags)) - return NULL; - - path = PyBytes_AsString(opath); - - Py_BEGIN_ALLOW_THREADS - res = fchmodat(dirfd, path, mode, flags); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif /* HAVE_FCHMODAT */ - -#ifdef HAVE_FCHOWNAT -PyDoc_STRVAR(posix_fchownat__doc__, -"fchownat(dirfd, path, uid, gid, flags=0)\n\n\ -Like chown() but if path is relative, it is taken as relative to dirfd.\n\ -flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_fchownat(PyObject *self, PyObject *args) -{ - PyObject *opath; - int dirfd, res; - long uid, gid; - int flags = 0; - char *path; - - if (!PyArg_ParseTuple(args, "iO&ll|i:fchownat", - &dirfd, PyUnicode_FSConverter, &opath, &uid, &gid, &flags)) - return NULL; - - path = PyBytes_AsString(opath); - - Py_BEGIN_ALLOW_THREADS - res = fchownat(dirfd, path, (uid_t) uid, (gid_t) gid, flags); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif /* HAVE_FCHOWNAT */ +#ifdef USE_XATTRS -#ifdef HAVE_FSTATAT -PyDoc_STRVAR(posix_fstatat__doc__, -"fstatat(dirfd, path, flags=0) -> stat result\n\n\ -Like stat() but if path is relative, it is taken as relative to dirfd.\n\ -flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); +PyDoc_STRVAR(posix_getxattr__doc__, +"getxattr(path, attribute, *, follow_symlinks=True) -> value\n\n\ +Return the value of extended attribute attribute on path.\n\ +\n\ +path may be either a string or an open file descriptor.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, getxattr will examine the symbolic link itself instead of the file\n\ + the link points to."); static PyObject * -posix_fstatat(PyObject *self, PyObject *args) +posix_getxattr(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *opath; - char *path; - STRUCT_STAT st; - int dirfd, res, flags = 0; + path_t path; + path_t attribute; + int follow_symlinks = 1; + PyObject *buffer = NULL; + int i; + static char *keywords[] = {"path", "attribute", "follow_symlinks", NULL}; - if (!PyArg_ParseTuple(args, "iO&|i:fstatat", - &dirfd, PyUnicode_FSConverter, &opath, &flags)) + memset(&path, 0, sizeof(path)); + memset(&attribute, 0, sizeof(attribute)); + path.allow_fd = 1; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|$p:getxattr", keywords, + path_converter, &path, + path_converter, &attribute, + &follow_symlinks)) return NULL; - path = PyBytes_AsString(opath); - - Py_BEGIN_ALLOW_THREADS - res = fstatat(dirfd, path, &st, flags); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res != 0) - return posix_error(); - - return _pystat_fromstructstat(&st); -} -#endif -#ifdef HAVE_FUTIMESAT -PyDoc_STRVAR(posix_futimesat__doc__, -"futimesat(dirfd, path[, (atime, mtime)])\n\ -Like utime() but if path is relative, it is taken as relative to dirfd.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_futimesat(PyObject *self, PyObject *args) -{ - PyObject *opath; - char *path; - int res, dirfd; - PyObject* arg = Py_None; - time_t atime, mtime; - long ansec, mnsec; - - if (!PyArg_ParseTuple(args, "iO&|O:futimesat", - &dirfd, PyUnicode_FSConverter, &opath, &arg)) - return NULL; - path = PyBytes_AsString(opath); - if (arg == Py_None) { - /* optional time values not given */ - Py_BEGIN_ALLOW_THREADS - res = futimesat(dirfd, path, NULL); - Py_END_ALLOW_THREADS - } - else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) { - PyErr_SetString(PyExc_TypeError, - "futimesat() arg 3 must be a tuple (atime, mtime)"); - Py_DECREF(opath); - return NULL; - } - else { - if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0), - &atime, &ansec) == -1) { - Py_DECREF(opath); - return NULL; - } - if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1), - &mtime, &mnsec) == -1) { - Py_DECREF(opath); - return NULL; - } + if (fd_and_follow_symlinks_invalid("getxattr", path.fd, follow_symlinks)) + goto exit; - Py_BEGIN_ALLOW_THREADS - { -#ifdef HAVE_UTIMENSAT - struct timespec buf[2]; - buf[0].tv_sec = atime; - buf[0].tv_nsec = ansec; - buf[1].tv_sec = mtime; - buf[1].tv_nsec = mnsec; - res = utimensat(dirfd, path, buf, 0); -#else - struct timeval buf[2]; - buf[0].tv_sec = atime; - buf[0].tv_usec = ansec / 1000; - buf[1].tv_sec = mtime; - buf[1].tv_usec = mnsec / 1000; - res = futimesat(dirfd, path, buf); -#endif + for (i = 0; ; i++) { + void *ptr; + ssize_t result; + static Py_ssize_t buffer_sizes[] = {128, XATTR_SIZE_MAX, 0}; + Py_ssize_t buffer_size = buffer_sizes[i]; + if (!buffer_size) { + path_error("getxattr", &path); + goto exit; } - Py_END_ALLOW_THREADS - } - Py_DECREF(opath); - if (res < 0) { - return posix_error(); - } - Py_RETURN_NONE; -} -#endif - -#ifdef HAVE_LINKAT -PyDoc_STRVAR(posix_linkat__doc__, -"linkat(srcfd, srcpath, dstfd, dstpath, flags=0)\n\n\ -Like link() but if srcpath is relative, it is taken as relative to srcfd\n\ -and if dstpath is relative, it is taken as relative to dstfd.\n\ -flags is optional and may be 0 or AT_SYMLINK_FOLLOW.\n\ -If srcpath is relative and srcfd is the special value AT_FDCWD, then\n\ -srcpath is interpreted relative to the current working directory. This\n\ -also applies for dstpath."); - -static PyObject * -posix_linkat(PyObject *self, PyObject *args) -{ - PyObject *osrc, *odst; - char *src, *dst; - int res, srcfd, dstfd; - int flags = 0; - - if (!PyArg_ParseTuple(args, "iO&iO&|i:linkat", - &srcfd, PyUnicode_FSConverter, &osrc, &dstfd, PyUnicode_FSConverter, &odst, &flags)) - return NULL; - src = PyBytes_AsString(osrc); - dst = PyBytes_AsString(odst); - Py_BEGIN_ALLOW_THREADS - res = linkat(srcfd, src, dstfd, dst, flags); - Py_END_ALLOW_THREADS - Py_DECREF(osrc); - Py_DECREF(odst); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif /* HAVE_LINKAT */ - -#ifdef HAVE_MKDIRAT -PyDoc_STRVAR(posix_mkdirat__doc__, -"mkdirat(dirfd, path, mode=0o777)\n\n\ -Like mkdir() but if path is relative, it is taken as relative to dirfd.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_mkdirat(PyObject *self, PyObject *args) -{ - int res, dirfd; - PyObject *opath; - char *path; - int mode = 0777; - - if (!PyArg_ParseTuple(args, "iO&|i:mkdirat", - &dirfd, PyUnicode_FSConverter, &opath, &mode)) - return NULL; - path = PyBytes_AsString(opath); - Py_BEGIN_ALLOW_THREADS - res = mkdirat(dirfd, path, mode); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif - -#if defined(HAVE_MKNODAT) && defined(HAVE_MAKEDEV) -PyDoc_STRVAR(posix_mknodat__doc__, -"mknodat(dirfd, path, mode=0o600, device=0)\n\n\ -Like mknod() but if path is relative, it is taken as relative to dirfd.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_mknodat(PyObject *self, PyObject *args) -{ - PyObject *opath; - char *filename; - int mode = 0600; - int device = 0; - int res, dirfd; - if (!PyArg_ParseTuple(args, "iO&|ii:mknodat", &dirfd, - PyUnicode_FSConverter, &opath, &mode, &device)) - return NULL; - filename = PyBytes_AS_STRING(opath); - Py_BEGIN_ALLOW_THREADS - res = mknodat(dirfd, filename, mode, device); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif - -#ifdef HAVE_OPENAT -PyDoc_STRVAR(posix_openat__doc__, -"openat(dirfd, path, flag, mode=0o777) -> fd\n\n\ -Like open() but if path is relative, it is taken as relative to dirfd.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_openat(PyObject *self, PyObject *args) -{ - PyObject *ofile; - char *file; - int flag, dirfd, fd; - int mode = 0777; - - if (!PyArg_ParseTuple(args, "iO&i|i:openat", - &dirfd, PyUnicode_FSConverter, &ofile, - &flag, &mode)) - return NULL; - file = PyBytes_AsString(ofile); - Py_BEGIN_ALLOW_THREADS - fd = openat(dirfd, file, flag, mode); - Py_END_ALLOW_THREADS - Py_DECREF(ofile); - if (fd < 0) - return posix_error(); - return PyLong_FromLong((long)fd); -} -#endif - -#ifdef HAVE_READLINKAT -PyDoc_STRVAR(posix_readlinkat__doc__, -"readlinkat(dirfd, path) -> path\n\n\ -Like readlink() but if path is relative, it is taken as relative to dirfd.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_readlinkat(PyObject *self, PyObject *args) -{ - PyObject *v, *opath; - char buf[MAXPATHLEN]; - char *path; - int n, dirfd; - int arg_is_unicode = 0; - - if (!PyArg_ParseTuple(args, "iO&:readlinkat", - &dirfd, PyUnicode_FSConverter, &opath)) - return NULL; - path = PyBytes_AsString(opath); - v = PySequence_GetItem(args, 1); - if (v == NULL) { - Py_DECREF(opath); - return NULL; - } - - if (PyUnicode_Check(v)) { - arg_is_unicode = 1; - } - Py_DECREF(v); - - Py_BEGIN_ALLOW_THREADS - n = readlinkat(dirfd, path, buf, (int) sizeof buf); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (n < 0) - return posix_error(); - - if (arg_is_unicode) - return PyUnicode_DecodeFSDefaultAndSize(buf, n); - else - return PyBytes_FromStringAndSize(buf, n); -} -#endif /* HAVE_READLINKAT */ - -#ifdef HAVE_RENAMEAT -PyDoc_STRVAR(posix_renameat__doc__, -"renameat(olddirfd, oldpath, newdirfd, newpath)\n\n\ -Like rename() but if oldpath is relative, it is taken as relative to\n\ -olddirfd and if newpath is relative, it is taken as relative to newdirfd.\n\ -If oldpath is relative and olddirfd is the special value AT_FDCWD, then\n\ -oldpath is interpreted relative to the current working directory. This\n\ -also applies for newpath."); - -static PyObject * -posix_renameat(PyObject *self, PyObject *args) -{ - int res; - PyObject *opathold, *opathnew; - char *opath, *npath; - int oldfd, newfd; - - if (!PyArg_ParseTuple(args, "iO&iO&:renameat", - &oldfd, PyUnicode_FSConverter, &opathold, &newfd, PyUnicode_FSConverter, &opathnew)) - return NULL; - opath = PyBytes_AsString(opathold); - npath = PyBytes_AsString(opathnew); - Py_BEGIN_ALLOW_THREADS - res = renameat(oldfd, opath, newfd, npath); - Py_END_ALLOW_THREADS - Py_DECREF(opathold); - Py_DECREF(opathnew); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif - -#if HAVE_SYMLINKAT -PyDoc_STRVAR(posix_symlinkat__doc__, -"symlinkat(src, dstfd, dst)\n\n\ -Like symlink() but if dst is relative, it is taken as relative to dstfd.\n\ -If dst is relative and dstfd is the special value AT_FDCWD, then dst\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_symlinkat(PyObject *self, PyObject *args) -{ - int res, dstfd; - PyObject *osrc, *odst; - char *src, *dst; - - if (!PyArg_ParseTuple(args, "O&iO&:symlinkat", - PyUnicode_FSConverter, &osrc, &dstfd, PyUnicode_FSConverter, &odst)) - return NULL; - src = PyBytes_AsString(osrc); - dst = PyBytes_AsString(odst); - Py_BEGIN_ALLOW_THREADS - res = symlinkat(src, dstfd, dst); - Py_END_ALLOW_THREADS - Py_DECREF(osrc); - Py_DECREF(odst); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif /* HAVE_SYMLINKAT */ - -#ifdef HAVE_UNLINKAT -PyDoc_STRVAR(posix_unlinkat__doc__, -"unlinkat(dirfd, path, flags=0)\n\n\ -Like unlink() but if path is relative, it is taken as relative to dirfd.\n\ -flags is optional and may be 0 or AT_REMOVEDIR. If AT_REMOVEDIR is\n\ -specified, unlinkat() behaves like rmdir().\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_unlinkat(PyObject *self, PyObject *args) -{ - int dirfd, res, flags = 0; - PyObject *opath; - char *path; - - if (!PyArg_ParseTuple(args, "iO&|i:unlinkat", - &dirfd, PyUnicode_FSConverter, &opath, &flags)) - return NULL; - path = PyBytes_AsString(opath); - Py_BEGIN_ALLOW_THREADS - res = unlinkat(dirfd, path, flags); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif - -#ifdef HAVE_UTIMENSAT -PyDoc_STRVAR(posix_utimensat__doc__, -"utimensat(dirfd, path[, atime=(atime_sec, atime_nsec),\n\ - mtime=(mtime_sec, mtime_nsec), flags=0])\n\ -utimensat(dirfd, path, None, None, flags)\n\n\ -Updates the timestamps of a file with nanosecond precision. If path is\n\ -relative, it is taken as relative to dirfd.\n\ -If atime and mtime are both None, which is the default, set atime and\n\ -mtime to the current time.\n\ -flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory.\n\ -If *_nsec is specified as UTIME_NOW, the timestamp is updated to the\n\ -current time.\n\ -If *_nsec is specified as UTIME_OMIT, the timestamp is not updated."); - -static PyObject * -posix_utimensat(PyObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *opath; - char *path; - int res, dirfd, flags = 0; - PyObject *atime = Py_None; - PyObject *mtime = Py_None; - - static char *kwlist[] = {"dirfd", "path", "atime", "mtime", "flags", NULL}; - - struct timespec buf[2]; + buffer = PyBytes_FromStringAndSize(NULL, buffer_size); + if (!buffer) + goto exit; + ptr = PyBytes_AS_STRING(buffer); - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO&|OOi:utimensat", kwlist, - &dirfd, PyUnicode_FSConverter, &opath, &atime, &mtime, &flags)) - return NULL; - path = PyBytes_AsString(opath); - if (atime == Py_None && mtime == Py_None) { - /* optional time values not given */ - Py_BEGIN_ALLOW_THREADS - res = utimensat(dirfd, path, NULL, flags); - Py_END_ALLOW_THREADS - } - else if (!PyTuple_Check(atime) || PyTuple_Size(atime) != 2) { - PyErr_SetString(PyExc_TypeError, - "utimensat() arg 3 must be a tuple (atime_sec, atime_nsec)"); - Py_DECREF(opath); - return NULL; - } - else if (!PyTuple_Check(mtime) || PyTuple_Size(mtime) != 2) { - PyErr_SetString(PyExc_TypeError, - "utimensat() arg 4 must be a tuple (mtime_sec, mtime_nsec)"); - Py_DECREF(opath); - return NULL; - } - else { - if (!PyArg_ParseTuple(atime, "ll:utimensat", - &(buf[0].tv_sec), &(buf[0].tv_nsec))) { - Py_DECREF(opath); - return NULL; - } - if (!PyArg_ParseTuple(mtime, "ll:utimensat", - &(buf[1].tv_sec), &(buf[1].tv_nsec))) { - Py_DECREF(opath); - return NULL; + Py_BEGIN_ALLOW_THREADS; + if (path.fd >= 0) + result = fgetxattr(path.fd, attribute.narrow, ptr, buffer_size); + else if (follow_symlinks) + result = getxattr(path.narrow, attribute.narrow, ptr, buffer_size); + else + result = lgetxattr(path.narrow, attribute.narrow, ptr, buffer_size); + Py_END_ALLOW_THREADS; + + if (result < 0) { + Py_DECREF(buffer); + buffer = NULL; + if (errno == ERANGE) + continue; + path_error("getxattr", &path); + goto exit; } - Py_BEGIN_ALLOW_THREADS - res = utimensat(dirfd, path, buf, flags); - Py_END_ALLOW_THREADS - } - Py_DECREF(opath); - if (res < 0) { - return posix_error(); - } - Py_RETURN_NONE; -} -#endif - -#ifdef HAVE_MKFIFOAT -PyDoc_STRVAR(posix_mkfifoat__doc__, -"mkfifoat(dirfd, path, mode=0o666)\n\n\ -Like mkfifo() but if path is relative, it is taken as relative to dirfd.\n\ -If path is relative and dirfd is the special value AT_FDCWD, then path\n\ -is interpreted relative to the current working directory."); - -static PyObject * -posix_mkfifoat(PyObject *self, PyObject *args) -{ - PyObject *opath; - char *filename; - int mode = 0666; - int res, dirfd; - if (!PyArg_ParseTuple(args, "iO&|i:mkfifoat", - &dirfd, PyUnicode_FSConverter, &opath, &mode)) - return NULL; - filename = PyBytes_AS_STRING(opath); - Py_BEGIN_ALLOW_THREADS - res = mkfifoat(dirfd, filename, mode); - Py_END_ALLOW_THREADS - Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; -} -#endif - -#ifdef USE_XATTRS -static int -try_getxattr(const char *path, const char *name, - ssize_t (*get)(const char *, const char *, void *, size_t), - Py_ssize_t buf_size, PyObject **res) -{ - PyObject *value; - Py_ssize_t len; - - assert(buf_size <= XATTR_SIZE_MAX); - value = PyBytes_FromStringAndSize(NULL, buf_size); - if (!value) - return 0; - Py_BEGIN_ALLOW_THREADS; - len = get(path, name, PyBytes_AS_STRING(value), buf_size); - Py_END_ALLOW_THREADS; - if (len < 0) { - Py_DECREF(value); - if (errno == ERANGE) { - value = NULL; - } - else { - posix_error(); - return 0; + if (result != buffer_size) { + /* Can only shrink. */ + _PyBytes_Resize(&buffer, result); } + break; } - else if (len != buf_size) { - /* Can only shrink. */ - _PyBytes_Resize(&value, len); - } - *res = value; - return 1; -} - -static PyObject * -getxattr_common(const char *path, PyObject *name_obj, - ssize_t (*get)(const char *, const char *, void *, size_t)) -{ - PyObject *value; - const char *name = PyBytes_AS_STRING(name_obj); - - /* Try a small value first. */ - if (!try_getxattr(path, name, get, 128, &value)) - return NULL; - if (value) - return value; - /* Now the maximum possible one. */ - if (!try_getxattr(path, name, get, XATTR_SIZE_MAX, &value)) - return NULL; - assert(value); - return value; -} - -PyDoc_STRVAR(posix_getxattr__doc__, -"getxattr(path, attr) -> value\n\n\ -Return the value of extended attribute *name* on *path*."); - -static PyObject * -posix_getxattr(PyObject *self, PyObject *args) -{ - PyObject *path, *res, *name; - - if (!PyArg_ParseTuple(args, "O&O&:getxattr", PyUnicode_FSConverter, &path, - PyUnicode_FSConverter, &name)) - return NULL; - res = getxattr_common(PyBytes_AS_STRING(path), name, getxattr); - Py_DECREF(path); - Py_DECREF(name); - return res; -} - -PyDoc_STRVAR(posix_lgetxattr__doc__, -"lgetxattr(path, attr) -> value\n\n\ -Like getxattr but don't follow symlinks."); - -static PyObject * -posix_lgetxattr(PyObject *self, PyObject *args) -{ - PyObject *path, *res, *name; - - if (!PyArg_ParseTuple(args, "O&O&:lgetxattr", PyUnicode_FSConverter, &path, - PyUnicode_FSConverter, &name)) - return NULL; - res = getxattr_common(PyBytes_AS_STRING(path), name, lgetxattr); - Py_DECREF(path); - Py_DECREF(name); - return res; -} - -static ssize_t -wrap_fgetxattr(const char *path, const char *name, void *value, size_t size) -{ - /* Hack to share code. */ - return fgetxattr((int)(Py_uintptr_t)path, name, value, size); -} -PyDoc_STRVAR(posix_fgetxattr__doc__, -"fgetxattr(fd, attr) -> value\n\n\ -Like getxattr but operate on a fd instead of a path."); - -static PyObject * -posix_fgetxattr(PyObject *self, PyObject *args) -{ - PyObject *res, *name; - int fd; - - if (!PyArg_ParseTuple(args, "iO&:fgetxattr", &fd, PyUnicode_FSConverter, &name)) - return NULL; - res = getxattr_common((const char *)(Py_uintptr_t)fd, name, wrap_fgetxattr); - Py_DECREF(name); - return res; +exit: + path_cleanup(&path); + path_cleanup(&attribute); + return buffer; } PyDoc_STRVAR(posix_setxattr__doc__, -"setxattr(path, attr, value, flags=0)\n\n\ -Set extended attribute *attr* on *path* to *value*."); +"setxattr(path, attribute, value, flags=0, *, follow_symlinks=True)\n\n\ +Set extended attribute attribute on path to value.\n\ +path may be either a string or an open file descriptor.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, setxattr will modify the symbolic link itself instead of the file\n\ + the link points to."); static PyObject * -posix_setxattr(PyObject *self, PyObject *args) +posix_setxattr(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *path, *name; - Py_buffer data; - int flags = 0, err; + path_t path; + path_t attribute; + Py_buffer value; + int flags = 0; + int follow_symlinks = 1; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "attribute", "value", + "flags", "follow_symlinks", NULL}; + + memset(&path, 0, sizeof(path)); + path.allow_fd = 1; + memset(&attribute, 0, sizeof(attribute)); + memset(&value, 0, sizeof(value)); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&y*|i$p:setxattr", + keywords, + path_converter, &path, + path_converter, &attribute, + &value, &flags, + &follow_symlinks)) + return NULL; + + if (fd_and_follow_symlinks_invalid("setxattr", path.fd, follow_symlinks)) + goto exit; - if (!PyArg_ParseTuple(args, "O&O&y*|i:setxattr", PyUnicode_FSConverter, - &path, PyUnicode_FSConverter, &name, &data, &flags)) - return NULL; Py_BEGIN_ALLOW_THREADS; - err = setxattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name), - data.buf, data.len, flags); + if (path.fd > -1) + result = fsetxattr(path.fd, attribute.narrow, + value.buf, value.len, flags); + else if (follow_symlinks) + result = setxattr(path.narrow, attribute.narrow, + value.buf, value.len, flags); + else + result = lsetxattr(path.narrow, attribute.narrow, + value.buf, value.len, flags); Py_END_ALLOW_THREADS; - Py_DECREF(path); - Py_DECREF(name); - PyBuffer_Release(&data); - if (err) - return posix_error(); - Py_RETURN_NONE; -} -PyDoc_STRVAR(posix_lsetxattr__doc__, -"lsetxattr(path, attr, value, flags=0)\n\n\ -Like setxattr but don't follow symlinks."); - -static PyObject * -posix_lsetxattr(PyObject *self, PyObject *args) -{ - PyObject *path, *name; - Py_buffer data; - int flags = 0, err; - - if (!PyArg_ParseTuple(args, "O&O&y*|i:lsetxattr", PyUnicode_FSConverter, - &path, PyUnicode_FSConverter, &name, &data, &flags)) - return NULL; - Py_BEGIN_ALLOW_THREADS; - err = lsetxattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name), - data.buf, data.len, flags); - Py_END_ALLOW_THREADS; - Py_DECREF(path); - Py_DECREF(name); - PyBuffer_Release(&data); - if (err) - return posix_error(); - Py_RETURN_NONE; -} + if (result) { + return_value = path_error("setxattr", &path); + goto exit; + } -PyDoc_STRVAR(posix_fsetxattr__doc__, -"fsetxattr(fd, attr, value, flags=0)\n\n\ -Like setxattr but operates on *fd* instead of a path."); + return_value = Py_None; + Py_INCREF(return_value); -static PyObject * -posix_fsetxattr(PyObject *self, PyObject *args) -{ - Py_buffer data; - const char *name; - int fd, flags = 0, err; +exit: + path_cleanup(&path); + path_cleanup(&attribute); + PyBuffer_Release(&value); - if (!PyArg_ParseTuple(args, "iO&y*|i:fsetxattr", &fd, PyUnicode_FSConverter, - &name, &data, &flags)) - return NULL; - Py_BEGIN_ALLOW_THREADS; - err = fsetxattr(fd, PyBytes_AS_STRING(name), data.buf, data.len, flags); - Py_END_ALLOW_THREADS; - Py_DECREF(name); - PyBuffer_Release(&data); - if (err) - return posix_error(); - Py_RETURN_NONE; + return return_value; } PyDoc_STRVAR(posix_removexattr__doc__, -"removexattr(path, attr)\n\n\ -Remove extended attribute *attr* on *path*."); - -static PyObject * -posix_removexattr(PyObject *self, PyObject *args) -{ - PyObject *path, *name; - int err; - - if (!PyArg_ParseTuple(args, "O&O&:removexattr", PyUnicode_FSConverter, &path, - PyUnicode_FSConverter, &name)) - return NULL; - Py_BEGIN_ALLOW_THREADS; - err = removexattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name)); - Py_END_ALLOW_THREADS; - Py_DECREF(path); - Py_DECREF(name); - if (err) - return posix_error(); - Py_RETURN_NONE; -} - -PyDoc_STRVAR(posix_lremovexattr__doc__, -"lremovexattr(path, attr)\n\n\ -Like removexattr but don't follow symlinks."); +"removexattr(path, attribute, *, follow_symlinks=True)\n\n\ +Remove extended attribute attribute on path.\n\ +path may be either a string or an open file descriptor.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, removexattr will modify the symbolic link itself instead of the file\n\ + the link points to."); static PyObject * -posix_lremovexattr(PyObject *self, PyObject *args) +posix_removexattr(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *path, *name; - int err; + path_t path; + path_t attribute; + int follow_symlinks = 1; + int result; + PyObject *return_value = NULL; + static char *keywords[] = {"path", "attribute", "follow_symlinks", NULL}; - if (!PyArg_ParseTuple(args, "O&O&:lremovexattr", PyUnicode_FSConverter, &path, - PyUnicode_FSConverter, &name)) + memset(&path, 0, sizeof(path)); + memset(&attribute, 0, sizeof(attribute)); + path.allow_fd = 1; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|$p:removexattr", + keywords, + path_converter, &path, + path_converter, &attribute, + &follow_symlinks)) return NULL; - Py_BEGIN_ALLOW_THREADS; - err = lremovexattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name)); - Py_END_ALLOW_THREADS; - Py_DECREF(path); - Py_DECREF(name); - if (err) - return posix_error(); - Py_RETURN_NONE; -} - -PyDoc_STRVAR(posix_fremovexattr__doc__, -"fremovexattr(fd, attr)\n\n\ -Like removexattr but operates on a file descriptor."); -static PyObject * -posix_fremovexattr(PyObject *self, PyObject *args) -{ - PyObject *name; - int fd, err; + if (fd_and_follow_symlinks_invalid("removexattr", path.fd, follow_symlinks)) + goto exit; - if (!PyArg_ParseTuple(args, "iO&:fremovexattr", &fd, - PyUnicode_FSConverter, &name)) - return NULL; Py_BEGIN_ALLOW_THREADS; - err = fremovexattr(fd, PyBytes_AS_STRING(name)); + if (path.fd > -1) + result = fremovexattr(path.fd, attribute.narrow); + else if (follow_symlinks) + result = removexattr(path.narrow, attribute.narrow); + else + result = lremovexattr(path.narrow, attribute.narrow); Py_END_ALLOW_THREADS; - Py_DECREF(name); - if (err) - return posix_error(); - Py_RETURN_NONE; -} - -static Py_ssize_t -try_listxattr(const char *path, ssize_t (*list)(const char *, char *, size_t), - Py_ssize_t buf_size, char **buf) -{ - Py_ssize_t len; - *buf = PyMem_MALLOC(buf_size); - if (!*buf) { - PyErr_NoMemory(); - return -1; - } - Py_BEGIN_ALLOW_THREADS; - len = list(path, *buf, buf_size); - Py_END_ALLOW_THREADS; - if (len < 0) { - PyMem_FREE(*buf); - if (errno != ERANGE) - posix_error(); - return -1; + if (result) { + return_value = path_error("removexattr", &path); + goto exit; } - return len; -} -static PyObject * -listxattr_common(const char *path, ssize_t (*list)(const char *, char *, size_t)) -{ - PyObject *res, *attr; - Py_ssize_t len, err, start, i; - char *buf; + return_value = Py_None; + Py_INCREF(return_value); - len = try_listxattr(path, list, 256, &buf); - if (len < 0) { - if (PyErr_Occurred()) - return NULL; - len = try_listxattr(path, list, XATTR_LIST_MAX, &buf); - if (len < 0) - return NULL; - } - res = PyList_New(0); - if (!res) { - PyMem_FREE(buf); - return NULL; - } - for (start = i = 0; i < len; i++) { - if (!buf[i]) { - attr = PyUnicode_DecodeFSDefaultAndSize(&buf[start], i - start); - if (!attr) { - Py_DECREF(res); - PyMem_FREE(buf); - return NULL; - } - err = PyList_Append(res, attr); - Py_DECREF(attr); - if (err) { - Py_DECREF(res); - PyMem_FREE(buf); - return NULL; - } - start = i + 1; - } - } - PyMem_FREE(buf); - return res; +exit: + path_cleanup(&path); + path_cleanup(&attribute); + + return return_value; } PyDoc_STRVAR(posix_listxattr__doc__, -"listxattr(path)\n\n\ -Return a list of extended attributes on *path*."); +"listxattr(path='.', *, follow_symlinks=True)\n\n\ +Return a list of extended attributes on path.\n\ +\n\ +path may be either None, a string, or an open file descriptor.\n\ +if path is None, listxattr will examine the current directory.\n\ +If follow_symlinks is False, and the last element of the path is a symbolic\n\ + link, listxattr will examine the symbolic link itself instead of the file\n\ + the link points to."); static PyObject * -posix_listxattr(PyObject *self, PyObject *args) +posix_listxattr(PyObject *self, PyObject *args, PyObject *kwargs) { - PyObject *path, *res; + path_t path; + int follow_symlinks = 1; + Py_ssize_t i; + PyObject *result = NULL; + char *buffer = NULL; + char *name; + static char *keywords[] = {"path", "follow_symlinks", NULL}; - if (!PyArg_ParseTuple(args, "O&:listxattr", PyUnicode_FSConverter, &path)) + memset(&path, 0, sizeof(path)); + path.allow_fd = 1; + path.fd = -1; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O&$p:listxattr", keywords, + path_converter, &path, + &follow_symlinks)) return NULL; - res = listxattr_common(PyBytes_AS_STRING(path), listxattr); - Py_DECREF(path); - return res; -} - -PyDoc_STRVAR(posix_llistxattr__doc__, -"llistxattr(path)\n\n\ -Like listxattr but don't follow symlinks.."); -static PyObject * -posix_llistxattr(PyObject *self, PyObject *args) -{ - PyObject *path, *res; + if (fd_and_follow_symlinks_invalid("listxattr", path.fd, follow_symlinks)) + goto exit; - if (!PyArg_ParseTuple(args, "O&:llistxattr", PyUnicode_FSConverter, &path)) - return NULL; - res = listxattr_common(PyBytes_AS_STRING(path), llistxattr); - Py_DECREF(path); - return res; -} + name = path.narrow ? path.narrow : "."; + for (i = 0; ; i++) { + char *start, *trace, *end; + ssize_t length; + static Py_ssize_t buffer_sizes[] = { 256, XATTR_LIST_MAX, 0 }; + Py_ssize_t buffer_size = buffer_sizes[i]; + if (!buffer_size) { + // ERANGE + path_error("listxattr", &path); + break; + } + buffer = PyMem_MALLOC(buffer_size); + if (!buffer) { + PyErr_NoMemory(); + break; + } -static ssize_t -wrap_flistxattr(const char *path, char *buf, size_t len) -{ - /* Hack to share code. */ - return flistxattr((int)(Py_uintptr_t)path, buf, len); -} + Py_BEGIN_ALLOW_THREADS; + if (path.fd > -1) + length = flistxattr(path.fd, buffer, buffer_size); + else if (follow_symlinks) + length = listxattr(name, buffer, buffer_size); + else + length = llistxattr(name, buffer, buffer_size); + Py_END_ALLOW_THREADS; -PyDoc_STRVAR(posix_flistxattr__doc__, -"flistxattr(path)\n\n\ -Like flistxattr but operates on a file descriptor."); + if (length < 0) { + if (errno == ERANGE) + continue; + path_error("listxattr", &path); + break; + } -static PyObject * -posix_flistxattr(PyObject *self, PyObject *args) -{ - long fd; + result = PyList_New(0); + if (!result) { + goto exit; + } - if (!PyArg_ParseTuple(args, "i:flistxattr", &fd)) - return NULL; - return listxattr_common((const char *)(Py_uintptr_t)fd, wrap_flistxattr); + end = buffer + length; + for (trace = start = buffer; trace != end; trace++) { + if (!*trace) { + int error; + PyObject *attribute = PyUnicode_DecodeFSDefaultAndSize(start, + trace - start); + if (!attribute) { + Py_DECREF(result); + result = NULL; + goto exit; + } + error = PyList_Append(result, attribute); + Py_DECREF(attribute); + if (error) { + Py_DECREF(result); + result = NULL; + goto exit; + } + start = trace + 1; + } + } + break; + } +exit: + path_cleanup(&path); + if (buffer) + PyMem_FREE(buffer); + return result; } #endif /* USE_XATTRS */ @@ -10600,20 +10715,30 @@ get_terminal_size(PyObject *self, PyObject *args) static PyMethodDef posix_methods[] = { - {"access", posix_access, METH_VARARGS, posix_access__doc__}, + {"access", (PyCFunction)posix_access, + METH_VARARGS | METH_KEYWORDS, + posix_access__doc__}, #ifdef HAVE_TTYNAME {"ttyname", posix_ttyname, METH_VARARGS, posix_ttyname__doc__}, #endif - {"chdir", posix_chdir, METH_VARARGS, posix_chdir__doc__}, + {"chdir", (PyCFunction)posix_chdir, + METH_VARARGS | METH_KEYWORDS, + posix_chdir__doc__}, #ifdef HAVE_CHFLAGS - {"chflags", posix_chflags, METH_VARARGS, posix_chflags__doc__}, + {"chflags", (PyCFunction)posix_chflags, + METH_VARARGS | METH_KEYWORDS, + posix_chflags__doc__}, #endif /* HAVE_CHFLAGS */ - {"chmod", posix_chmod, METH_VARARGS, posix_chmod__doc__}, + {"chmod", (PyCFunction)posix_chmod, + METH_VARARGS | METH_KEYWORDS, + posix_chmod__doc__}, #ifdef HAVE_FCHMOD {"fchmod", posix_fchmod, METH_VARARGS, posix_fchmod__doc__}, #endif /* HAVE_FCHMOD */ #ifdef HAVE_CHOWN - {"chown", posix_chown, METH_VARARGS, posix_chown__doc__}, + {"chown", (PyCFunction)posix_chown, + METH_VARARGS | METH_KEYWORDS, + posix_chown__doc__}, #endif /* HAVE_CHOWN */ #ifdef HAVE_LCHMOD {"lchmod", posix_lchmod, METH_VARARGS, posix_lchmod__doc__}, @@ -10639,15 +10764,20 @@ static PyMethodDef posix_methods[] = { {"getcwdb", (PyCFunction)posix_getcwd_bytes, METH_NOARGS, posix_getcwdb__doc__}, #endif -#ifdef HAVE_LINK - {"link", posix_link, METH_VARARGS, posix_link__doc__}, +#if defined(HAVE_LINK) || defined(MS_WINDOWS) + {"link", (PyCFunction)posix_link, + METH_VARARGS | METH_KEYWORDS, + posix_link__doc__}, #endif /* HAVE_LINK */ - {"listdir", posix_listdir, METH_VARARGS, posix_listdir__doc__}, -#ifdef HAVE_FDOPENDIR - {"flistdir", posix_flistdir, METH_VARARGS, posix_flistdir__doc__}, -#endif - {"lstat", posix_lstat, METH_VARARGS, posix_lstat__doc__}, - {"mkdir", posix_mkdir, METH_VARARGS, posix_mkdir__doc__}, + {"listdir", (PyCFunction)posix_listdir, + METH_VARARGS | METH_KEYWORDS, + posix_listdir__doc__}, + {"lstat", (PyCFunction)posix_lstat, + METH_VARARGS | METH_KEYWORDS, + posix_lstat__doc__}, + {"mkdir", (PyCFunction)posix_mkdir, + METH_VARARGS | METH_KEYWORDS, + posix_mkdir__doc__}, #ifdef HAVE_NICE {"nice", posix_nice, METH_VARARGS, posix_nice__doc__}, #endif /* HAVE_NICE */ @@ -10658,23 +10788,31 @@ static PyMethodDef posix_methods[] = { {"setpriority", posix_setpriority, METH_VARARGS, posix_setpriority__doc__}, #endif /* HAVE_SETPRIORITY */ #ifdef HAVE_READLINK - {"readlink", posix_readlink, METH_VARARGS, posix_readlink__doc__}, + {"readlink", (PyCFunction)posix_readlink, + METH_VARARGS | METH_KEYWORDS, + readlink__doc__}, #endif /* HAVE_READLINK */ #if !defined(HAVE_READLINK) && defined(MS_WINDOWS) - {"readlink", win_readlink, METH_VARARGS, win_readlink__doc__}, + {"readlink", (PyCFunction)win_readlink, + METH_VARARGS | METH_KEYWORDS, + readlink__doc__}, #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */ - {"rename", posix_rename, METH_VARARGS, posix_rename__doc__}, - {"replace", posix_replace, METH_VARARGS, posix_replace__doc__}, + {"rename", (PyCFunction)posix_rename, + METH_VARARGS | METH_KEYWORDS, + posix_rename__doc__}, + {"replace", (PyCFunction)posix_replace, + METH_VARARGS | METH_KEYWORDS, + posix_replace__doc__}, {"rmdir", posix_rmdir, METH_VARARGS, posix_rmdir__doc__}, - {"stat", posix_stat, METH_VARARGS, posix_stat__doc__}, + {"stat", (PyCFunction)posix_stat, + METH_VARARGS | METH_KEYWORDS, + posix_stat__doc__}, {"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__}, -#if defined(HAVE_SYMLINK) && !defined(MS_WINDOWS) - {"symlink", posix_symlink, METH_VARARGS, posix_symlink__doc__}, +#if defined(HAVE_SYMLINK) + {"symlink", (PyCFunction)posix_symlink, + METH_VARARGS | METH_KEYWORDS, + posix_symlink__doc__}, #endif /* HAVE_SYMLINK */ -#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) - {"symlink", (PyCFunction)win_symlink, METH_VARARGS | METH_KEYWORDS, - win_symlink__doc__}, -#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */ #ifdef HAVE_SYSTEM {"system", posix_system, METH_VARARGS, posix_system__doc__}, #endif @@ -10682,29 +10820,24 @@ static PyMethodDef posix_methods[] = { #ifdef HAVE_UNAME {"uname", posix_uname, METH_NOARGS, posix_uname__doc__}, #endif /* HAVE_UNAME */ - {"unlink", posix_unlink, METH_VARARGS, posix_unlink__doc__}, - {"remove", posix_unlink, METH_VARARGS, posix_remove__doc__}, + {"unlink", (PyCFunction)posix_unlink, + METH_VARARGS | METH_KEYWORDS, + posix_unlink__doc__}, + {"remove", (PyCFunction)posix_unlink, + METH_VARARGS | METH_KEYWORDS, + posix_remove__doc__}, {"utime", (PyCFunction)posix_utime, METH_VARARGS | METH_KEYWORDS, posix_utime__doc__}, -#ifdef HAVE_FUTIMES - {"futimes", (PyCFunction)posix_futimes, - METH_VARARGS | METH_KEYWORDS, posix_futimes__doc__}, -#endif -#ifdef HAVE_LUTIMES - {"lutimes", (PyCFunction)posix_lutimes, - METH_VARARGS | METH_KEYWORDS, posix_lutimes__doc__}, -#endif #ifdef HAVE_TIMES {"times", posix_times, METH_NOARGS, posix_times__doc__}, #endif /* HAVE_TIMES */ {"_exit", posix__exit, METH_VARARGS, posix__exit__doc__}, #ifdef HAVE_EXECV {"execv", posix_execv, METH_VARARGS, posix_execv__doc__}, - {"execve", posix_execve, METH_VARARGS, posix_execve__doc__}, + {"execve", (PyCFunction)posix_execve, + METH_VARARGS | METH_KEYWORDS, + posix_execve__doc__}, #endif /* HAVE_EXECV */ -#ifdef HAVE_FEXECVE - {"fexecve", posix_fexecve, METH_VARARGS, posix_fexecve__doc__}, -#endif #ifdef HAVE_SPAWNV {"spawnv", posix_spawnv, METH_VARARGS, posix_spawnv__doc__}, {"spawnve", posix_spawnve, METH_VARARGS, posix_spawnve__doc__}, @@ -10791,7 +10924,6 @@ static PyMethodDef posix_methods[] = { #ifdef MS_WINDOWS {"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__}, {"kill", win32_kill, METH_VARARGS, win32_kill__doc__}, - {"link", win32_link, METH_VARARGS, win32_link__doc__}, #endif #ifdef HAVE_SETUID {"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__}, @@ -10853,7 +10985,9 @@ static PyMethodDef posix_methods[] = { #ifdef HAVE_TCSETPGRP {"tcsetpgrp", posix_tcsetpgrp, METH_VARARGS, posix_tcsetpgrp__doc__}, #endif /* HAVE_TCSETPGRP */ - {"open", posix_open, METH_VARARGS, posix_open__doc__}, + {"open", (PyCFunction)posix_open,\ + METH_VARARGS | METH_KEYWORDS, + posix_open__doc__}, {"close", posix_close, METH_VARARGS, posix_close__doc__}, {"closerange", posix_closerange, METH_VARARGS, posix_closerange__doc__}, {"device_encoding", device_encoding, METH_VARARGS, device_encoding__doc__}, @@ -10890,10 +11024,14 @@ static PyMethodDef posix_methods[] = { {"pipe2", posix_pipe2, METH_O, posix_pipe2__doc__}, #endif #ifdef HAVE_MKFIFO - {"mkfifo", posix_mkfifo, METH_VARARGS, posix_mkfifo__doc__}, + {"mkfifo", (PyCFunction)posix_mkfifo, + METH_VARARGS | METH_KEYWORDS, + posix_mkfifo__doc__}, #endif #if defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV) - {"mknod", posix_mknod, METH_VARARGS, posix_mknod__doc__}, + {"mknod", (PyCFunction)posix_mknod, + METH_VARARGS | METH_KEYWORDS, + posix_mknod__doc__}, #endif #ifdef HAVE_DEVICE_MACROS {"major", posix_major, METH_VARARGS, posix_major__doc__}, @@ -10961,7 +11099,9 @@ static PyMethodDef posix_methods[] = { {"fstatvfs", posix_fstatvfs, METH_VARARGS, posix_fstatvfs__doc__}, #endif #if defined(HAVE_STATVFS) && defined(HAVE_SYS_STATVFS_H) - {"statvfs", posix_statvfs, METH_VARARGS, posix_statvfs__doc__}, + {"statvfs", (PyCFunction)posix_statvfs, + METH_VARARGS | METH_KEYWORDS, + posix_statvfs__doc__}, #endif #ifdef HAVE_CONFSTR {"confstr", posix_confstr, METH_VARARGS, posix_confstr__doc__}, @@ -11000,67 +11140,19 @@ static PyMethodDef posix_methods[] = { {"getresgid", posix_getresgid, METH_NOARGS, posix_getresgid__doc__}, #endif -/* posix *at family of functions */ -#ifdef HAVE_FACCESSAT - {"faccessat", posix_faccessat, METH_VARARGS, posix_faccessat__doc__}, -#endif -#ifdef HAVE_FCHMODAT - {"fchmodat", posix_fchmodat, METH_VARARGS, posix_fchmodat__doc__}, -#endif /* HAVE_FCHMODAT */ -#ifdef HAVE_FCHOWNAT - {"fchownat", posix_fchownat, METH_VARARGS, posix_fchownat__doc__}, -#endif /* HAVE_FCHOWNAT */ -#ifdef HAVE_FSTATAT - {"fstatat", posix_fstatat, METH_VARARGS, posix_fstatat__doc__}, -#endif -#ifdef HAVE_FUTIMESAT - {"futimesat", posix_futimesat, METH_VARARGS, posix_futimesat__doc__}, -#endif -#ifdef HAVE_LINKAT - {"linkat", posix_linkat, METH_VARARGS, posix_linkat__doc__}, -#endif /* HAVE_LINKAT */ -#ifdef HAVE_MKDIRAT - {"mkdirat", posix_mkdirat, METH_VARARGS, posix_mkdirat__doc__}, -#endif -#if defined(HAVE_MKNODAT) && defined(HAVE_MAKEDEV) - {"mknodat", posix_mknodat, METH_VARARGS, posix_mknodat__doc__}, -#endif -#ifdef HAVE_OPENAT - {"openat", posix_openat, METH_VARARGS, posix_openat__doc__}, -#endif -#ifdef HAVE_READLINKAT - {"readlinkat", posix_readlinkat, METH_VARARGS, posix_readlinkat__doc__}, -#endif /* HAVE_READLINKAT */ -#ifdef HAVE_RENAMEAT - {"renameat", posix_renameat, METH_VARARGS, posix_renameat__doc__}, -#endif -#if HAVE_SYMLINKAT - {"symlinkat", posix_symlinkat, METH_VARARGS, posix_symlinkat__doc__}, -#endif /* HAVE_SYMLINKAT */ -#ifdef HAVE_UNLINKAT - {"unlinkat", posix_unlinkat, METH_VARARGS, posix_unlinkat__doc__}, -#endif -#ifdef HAVE_UTIMENSAT - {"utimensat", (PyCFunction)posix_utimensat, - METH_VARARGS | METH_KEYWORDS, - posix_utimensat__doc__}, -#endif -#ifdef HAVE_MKFIFOAT - {"mkfifoat", posix_mkfifoat, METH_VARARGS, posix_mkfifoat__doc__}, -#endif #ifdef USE_XATTRS - {"setxattr", posix_setxattr, METH_VARARGS, posix_setxattr__doc__}, - {"lsetxattr", posix_lsetxattr, METH_VARARGS, posix_lsetxattr__doc__}, - {"fsetxattr", posix_fsetxattr, METH_VARARGS, posix_fsetxattr__doc__}, - {"getxattr", posix_getxattr, METH_VARARGS, posix_getxattr__doc__}, - {"lgetxattr", posix_lgetxattr, METH_VARARGS, posix_lgetxattr__doc__}, - {"fgetxattr", posix_fgetxattr, METH_VARARGS, posix_fgetxattr__doc__}, - {"removexattr", posix_removexattr, METH_VARARGS, posix_removexattr__doc__}, - {"lremovexattr", posix_lremovexattr, METH_VARARGS, posix_lremovexattr__doc__}, - {"fremovexattr", posix_fremovexattr, METH_VARARGS, posix_fremovexattr__doc__}, - {"listxattr", posix_listxattr, METH_VARARGS, posix_listxattr__doc__}, - {"llistxattr", posix_llistxattr, METH_VARARGS, posix_llistxattr__doc__}, - {"flistxattr", posix_flistxattr, METH_VARARGS, posix_flistxattr__doc__}, + {"setxattr", (PyCFunction)posix_setxattr, + METH_VARARGS | METH_KEYWORDS, + posix_setxattr__doc__}, + {"getxattr", (PyCFunction)posix_getxattr, + METH_VARARGS | METH_KEYWORDS, + posix_getxattr__doc__}, + {"removexattr", (PyCFunction)posix_removexattr, + METH_VARARGS | METH_KEYWORDS, + posix_removexattr__doc__}, + {"listxattr", (PyCFunction)posix_listxattr, + METH_VARARGS | METH_KEYWORDS, + posix_listxattr__doc__}, #endif #if defined(TERMSIZE_USE_CONIO) || defined(TERMSIZE_USE_IOCTL) {"get_terminal_size", get_terminal_size, METH_VARARGS, termsize__doc__}, @@ -11268,28 +11360,6 @@ all_ins(PyObject *d) #ifdef O_ACCMODE if (ins(d, "O_ACCMODE", (long)O_ACCMODE)) return -1; #endif -/* posix - constants for *at functions */ -#ifdef AT_SYMLINK_NOFOLLOW - if (ins(d, "AT_SYMLINK_NOFOLLOW", (long)AT_SYMLINK_NOFOLLOW)) return -1; -#endif -#ifdef AT_EACCESS - if (ins(d, "AT_EACCESS", (long)AT_EACCESS)) return -1; -#endif -#ifdef AT_FDCWD - if (ins(d, "AT_FDCWD", (long)AT_FDCWD)) return -1; -#endif -#ifdef AT_REMOVEDIR - if (ins(d, "AT_REMOVEDIR", (long)AT_REMOVEDIR)) return -1; -#endif -#ifdef AT_SYMLINK_FOLLOW - if (ins(d, "AT_SYMLINK_FOLLOW", (long)AT_SYMLINK_FOLLOW)) return -1; -#endif -#ifdef UTIME_NOW - if (ins(d, "UTIME_NOW", (long)UTIME_NOW)) return -1; -#endif -#ifdef UTIME_OMIT - if (ins(d, "UTIME_OMIT", (long)UTIME_OMIT)) return -1; -#endif #ifdef SEEK_HOLE @@ -11483,14 +11553,6 @@ all_ins(PyObject *d) if (ins(d, "F_TEST", (long)F_TEST)) return -1; #endif - /* constants for futimens */ -#ifdef UTIME_NOW - if (ins(d, "UTIME_NOW", (long)UTIME_NOW)) return -1; -#endif -#ifdef UTIME_OMIT - if (ins(d, "UTIME_OMIT", (long)UTIME_OMIT)) return -1; -#endif - #ifdef HAVE_SPAWNV #if defined(PYOS_OS2) && defined(PYCC_GCC) if (ins(d, "P_WAIT", (long)P_WAIT)) return -1; @@ -11613,10 +11675,130 @@ static struct PyModuleDef posixmodule = { }; +static char *have_functions[] = { + +#ifdef HAVE_FACCESSAT + "HAVE_FACCESSAT", +#endif + +#ifdef HAVE_FCHDIR + "HAVE_FCHDIR", +#endif + +#ifdef HAVE_FCHMOD + "HAVE_FCHMOD", +#endif + +#ifdef HAVE_FCHMODAT + "HAVE_FCHMODAT", +#endif + +#ifdef HAVE_FCHOWN + "HAVE_FCHOWN", +#endif + +#ifdef HAVE_FEXECVE + "HAVE_FEXECVE", +#endif + +#ifdef HAVE_FDOPENDIR + "HAVE_FDOPENDIR", +#endif + +#ifdef HAVE_FSTATAT + "HAVE_FSTATAT", +#endif + +#ifdef HAVE_FSTATVFS + "HAVE_FSTATVFS", +#endif + +#ifdef HAVE_FUTIMENS + "HAVE_FUTIMENS", +#endif + +#ifdef HAVE_FUTIMES + "HAVE_FUTIMES", +#endif + +#ifdef HAVE_FUTIMESAT + "HAVE_FUTIMESAT", +#endif + +#ifdef HAVE_LINKAT + "HAVE_LINKAT", +#endif + +#ifdef HAVE_LCHFLAGS + "HAVE_LCHFLAGS", +#endif + +#ifdef HAVE_LCHMOD + "HAVE_LCHMOD", +#endif + +#ifdef HAVE_LCHOWN + "HAVE_LCHOWN", +#endif + +#ifdef HAVE_LSTAT + "HAVE_LSTAT", +#endif + +#ifdef HAVE_LUTIMES + "HAVE_LUTIMES", +#endif + +#ifdef HAVE_MKDIRAT + "HAVE_MKDIRAT", +#endif + +#ifdef HAVE_MKFIFOAT + "HAVE_MKFIFOAT", +#endif + +#ifdef HAVE_MKNODAT + "HAVE_MKNODAT", +#endif + +#ifdef HAVE_OPENAT + "HAVE_OPENAT", +#endif + +#ifdef HAVE_READLINKAT + "HAVE_READLINKAT", +#endif + +#ifdef HAVE_RENAMEAT + "HAVE_RENAMEAT", +#endif + +#ifdef HAVE_SYMLINKAT + "HAVE_SYMLINKAT", +#endif + +#ifdef HAVE_UNLINKAT + "HAVE_UNLINKAT", +#endif + +#ifdef HAVE_UTIMENSAT + "HAVE_UTIMENSAT", +#endif + +#ifdef MS_WINDOWS + "MS_WINDOWS", +#endif + + NULL +}; + + PyMODINIT_FUNC INITFUNC(void) { PyObject *m, *v; + PyObject *list; + char **trace; #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) win32_can_symlink = enable_symlink(); @@ -11704,7 +11886,6 @@ INITFUNC(void) Py_INCREF(&SchedParamType); PyModule_AddObject(m, "sched_param", (PyObject *)&SchedParamType); #endif - initialized = 1; #ifdef __APPLE__ /* @@ -11714,7 +11895,7 @@ INITFUNC(void) * currently active platform. * * This block allow one to use a python binary that was build on - * OSX 10.4 on OSX 10.3, without loosing access to new APIs on + * OSX 10.4 on OSX 10.3, without losing access to new APIs on * OSX 10.4. */ #ifdef HAVE_FSTATVFS @@ -11750,6 +11931,35 @@ INITFUNC(void) if (!billion) return NULL; + /* suppress "function not used" warnings */ + { + int ignored; + fd_specified("", -1); + follow_symlinks_specified("", 1); + dir_fd_and_follow_symlinks_invalid("chmod", DEFAULT_DIR_FD, 1); + dir_fd_converter(Py_None, &ignored); + dir_fd_unavailable(Py_None, &ignored); + } + + /* + * provide list of locally available functions + * so os.py can populate support_* lists + */ + list = PyList_New(0); + if (!list) + return NULL; + for (trace = have_functions; *trace; trace++) { + PyObject *unicode = PyUnicode_DecodeASCII(*trace, strlen(*trace), NULL); + if (!unicode) + return NULL; + if (PyList_Append(list, unicode)) + return NULL; + Py_DECREF(unicode); + } + PyModule_AddObject(m, "_have_functions", list); + + initialized = 1; + return m; } |