Author: Ronan Lamy <ronan.l...@gmail.com> Branch: Changeset: r83540:70a0c992daf3 Date: 2016-04-06 15:58 +0100 http://bitbucket.org/pypy/pypy/changeset/70a0c992daf3/
Log: Merge branch 'rposix-for-3' diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py --- a/rpython/rlib/rposix.py +++ b/rpython/rlib/rposix.py @@ -22,21 +22,6 @@ from rpython.rlib import rwin32 from rpython.rlib.rwin32file import make_win32_traits -class CConfig: - _compilation_info_ = ExternalCompilationInfo( - includes=['sys/stat.h', - 'unistd.h', - 'fcntl.h'], - ) - for _name in """fchdir fchmod fchmodat fchown fchownat fexecve fdopendir - fpathconf fstat fstatat fstatvfs ftruncate futimens futimes - futimesat linkat lchflags lchmod lchown lstat lutimes - mkdirat mkfifoat mknodat openat readlinkat renameat - symlinkat unlinkat utimensat""".split(): - locals()['HAVE_%s' % _name.upper()] = rffi_platform.Has(_name) -cConfig = rffi_platform.configure(CConfig) -globals().update(cConfig) - class CConstantErrno(CConstant): # these accessors are used when calling get_errno() or set_errno() @@ -618,14 +603,44 @@ config = rffi_platform.configure(CConfig) DIRENT = config['DIRENT'] DIRENTP = lltype.Ptr(DIRENT) - c_opendir = external('opendir', [rffi.CCHARP], DIRP, - save_err=rffi.RFFI_SAVE_ERRNO) + c_opendir = external('opendir', + [rffi.CCHARP], DIRP, save_err=rffi.RFFI_SAVE_ERRNO) + c_fdopendir = external('fdopendir', + [rffi.INT], DIRP, save_err=rffi.RFFI_SAVE_ERRNO) # XXX macro=True is hack to make sure we get the correct kind of # dirent struct (which depends on defines) c_readdir = external('readdir', [DIRP], DIRENTP, macro=True, save_err=rffi.RFFI_FULL_ERRNO_ZERO) c_closedir = external('closedir', [DIRP], rffi.INT) +def _listdir(dirp): + result = [] + while True: + direntp = c_readdir(dirp) + if not direntp: + error = get_saved_errno() + break + namep = rffi.cast(rffi.CCHARP, direntp.c_d_name) + name = rffi.charp2str(namep) + if name != '.' and name != '..': + result.append(name) + c_closedir(dirp) + if error: + raise OSError(error, "readdir failed") + return result + +def fdlistdir(dirfd): + """ + Like listdir(), except that the directory is specified as an open + file descriptor. + + Note: fdlistdir() closes the file descriptor. + """ + dirp = c_fdopendir(dirfd) + if not dirp: + raise OSError(get_saved_errno(), "opendir failed") + return _listdir(dirp) + @replace_os_function('listdir') @specialize.argtype(0) def listdir(path): @@ -634,20 +649,7 @@ dirp = c_opendir(path) if not dirp: raise OSError(get_saved_errno(), "opendir failed") - result = [] - while True: - direntp = c_readdir(dirp) - if not direntp: - error = get_saved_errno() - break - namep = rffi.cast(rffi.CCHARP, direntp.c_d_name) - name = rffi.charp2str(namep) - if name != '.' and name != '..': - result.append(name) - c_closedir(dirp) - if error: - raise OSError(error, "readdir failed") - return result + return _listdir(dirp) else: # _WIN32 case traits = _preferred_traits(path) win32traits = make_win32_traits(traits) @@ -1739,3 +1741,259 @@ def getcontroller(self): from rpython.rlib.rposix_environ import OsEnvironController return OsEnvironController() + + +# ____________________________________________________________ +# Support for f... and ...at families of POSIX functions + +class CConfig: + _compilation_info_ = ExternalCompilationInfo( + includes=['sys/stat.h', + 'unistd.h', + 'fcntl.h'], + ) + for _name in """faccessat fchdir fchmod fchmodat fchown fchownat fexecve + fdopendir fpathconf fstat fstatat fstatvfs ftruncate + futimens futimes futimesat linkat chflags lchflags lchmod lchown + lstat lutimes mkdirat mkfifoat mknodat openat readlinkat renameat + symlinkat unlinkat utimensat""".split(): + locals()['HAVE_%s' % _name.upper()] = rffi_platform.Has(_name) +cConfig = rffi_platform.configure(CConfig) +globals().update(cConfig) + +if not _WIN32: + class CConfig: + _compilation_info_ = ExternalCompilationInfo( + includes=['sys/stat.h', + 'unistd.h', + 'fcntl.h'], + ) + AT_FDCWD = rffi_platform.DefinedConstantInteger('AT_FDCWD') + AT_SYMLINK_NOFOLLOW = rffi_platform.DefinedConstantInteger('AT_SYMLINK_NOFOLLOW') + AT_EACCESS = rffi_platform.DefinedConstantInteger('AT_EACCESS') + AT_REMOVEDIR = rffi_platform.DefinedConstantInteger('AT_REMOVEDIR') + AT_EMPTY_PATH = rffi_platform.DefinedConstantInteger('AT_EMPTY_PATH') + UTIME_NOW = rffi_platform.DefinedConstantInteger('UTIME_NOW') + UTIME_OMIT = rffi_platform.DefinedConstantInteger('UTIME_OMIT') + TIMESPEC = rffi_platform.Struct('struct timespec', [ + ('tv_sec', rffi.TIME_T), + ('tv_nsec', rffi.LONG)]) + + cConfig = rffi_platform.configure(CConfig) + globals().update(cConfig) + TIMESPEC2P = rffi.CArrayPtr(TIMESPEC) + +if HAVE_FACCESSAT: + c_faccessat = external('faccessat', + [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT) + + def faccessat(pathname, mode, dir_fd=AT_FDCWD, + effective_ids=False, follow_symlinks=True): + """Thin wrapper around faccessat(2) with an interface simlar to + Python3's os.access(). + """ + flags = 0 + if not follow_symlinks: + flags |= AT_SYMLINK_NOFOLLOW + if effective_ids: + flags |= AT_EACCESS + error = c_faccessat(dir_fd, pathname, mode, flags) + return error == 0 + +if HAVE_FCHMODAT: + c_fchmodat = external('fchmodat', + [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO,) + + def fchmodat(path, mode, dir_fd=AT_FDCWD, follow_symlinks=True): + if follow_symlinks: + flag = 0 + else: + flag = AT_SYMLINK_NOFOLLOW + error = c_fchmodat(dir_fd, path, mode, flag) + handle_posix_error('fchmodat', error) + +if HAVE_FCHOWNAT: + c_fchownat = external('fchownat', + [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT, rffi.INT], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO,) + + def fchownat(path, owner, group, dir_fd=AT_FDCWD, + follow_symlinks=True, empty_path=False): + flag = 0 + if not follow_symlinks: + flag |= AT_SYMLINK_NOFOLLOW + if empty_path: + flag |= AT_EMPTY_PATH + error = c_fchownat(dir_fd, path, owner, group, flag) + handle_posix_error('fchownat', error) + +if HAVE_FEXECVE: + c_fexecve = external('fexecve', + [rffi.INT, rffi.CCHARPP, rffi.CCHARPP], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def fexecve(fd, args, env): + envstrs = [] + for item in env.iteritems(): + envstr = "%s=%s" % item + envstrs.append(envstr) + + # This list conversion already takes care of NUL bytes. + l_args = rffi.ll_liststr2charpp(args) + l_env = rffi.ll_liststr2charpp(envstrs) + c_fexecve(fd, l_args, l_env) + + rffi.free_charpp(l_env) + rffi.free_charpp(l_args) + raise OSError(get_saved_errno(), "execve failed") + +if HAVE_LINKAT: + c_linkat = external('linkat', + [rffi.INT, rffi.CCHARP, rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT) + + def linkat(src, dst, src_dir_fd=AT_FDCWD, dst_dir_fd=AT_FDCWD, + follow_symlinks=True): + """Thin wrapper around linkat(2) with an interface similar to + Python3's os.link() + """ + if follow_symlinks: + flag = 0 + else: + flag = AT_SYMLINK_NOFOLLOW + error = c_linkat(src_dir_fd, src, dst_dir_fd, dst, flag) + handle_posix_error('linkat', error) + +if HAVE_FUTIMENS: + c_futimens = external('futimens', [rffi.INT, TIMESPEC2P], rffi.INT) + + def futimens(fd, atime, atime_ns, mtime, mtime_ns): + l_times = lltype.malloc(TIMESPEC2P.TO, 2, flavor='raw') + rffi.setintfield(l_times[0], 'c_tv_sec', atime) + rffi.setintfield(l_times[0], 'c_tv_nsec', atime_ns) + rffi.setintfield(l_times[1], 'c_tv_sec', mtime) + rffi.setintfield(l_times[1], 'c_tv_nsec', mtime_ns) + error = c_futimens(fd, l_times) + lltype.free(l_times, flavor='raw') + handle_posix_error('futimens', error) + +if HAVE_UTIMENSAT: + c_utimensat = external('utimensat', + [rffi.INT, rffi.CCHARP, TIMESPEC2P, rffi.INT], rffi.INT) + + def utimensat(pathname, atime, atime_ns, mtime, mtime_ns, + dir_fd=AT_FDCWD, follow_symlinks=True): + """Wrapper around utimensat(2) + + To set access time to the current time, pass atime_ns=UTIME_NOW, + atime is then ignored. + + To set modification time to the current time, pass mtime_ns=UTIME_NOW, + mtime is then ignored. + """ + l_times = lltype.malloc(TIMESPEC2P.TO, 2, flavor='raw') + rffi.setintfield(l_times[0], 'c_tv_sec', atime) + rffi.setintfield(l_times[0], 'c_tv_nsec', atime_ns) + rffi.setintfield(l_times[1], 'c_tv_sec', mtime) + rffi.setintfield(l_times[1], 'c_tv_nsec', mtime_ns) + if follow_symlinks: + flag = 0 + else: + flag = AT_SYMLINK_NOFOLLOW + error = c_utimensat(dir_fd, pathname, l_times, flag) + lltype.free(l_times, flavor='raw') + handle_posix_error('utimensat', error) + +if HAVE_MKDIRAT: + c_mkdirat = external('mkdirat', + [rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def mkdirat(pathname, mode, dir_fd=AT_FDCWD): + error = c_mkdirat(dir_fd, pathname, mode) + handle_posix_error('mkdirat', error) + +if HAVE_UNLINKAT: + c_unlinkat = external('unlinkat', + [rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def unlinkat(pathname, dir_fd=AT_FDCWD, removedir=False): + flag = AT_REMOVEDIR if removedir else 0 + error = c_unlinkat(dir_fd, pathname, flag) + handle_posix_error('unlinkat', error) + +if HAVE_READLINKAT: + c_readlinkat = external( + 'readlinkat', + [rffi.INT, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T, + save_err=rffi.RFFI_SAVE_ERRNO) + + def readlinkat(pathname, dir_fd=AT_FDCWD): + pathname = _as_bytes0(pathname) + bufsize = 1023 + while True: + buf = lltype.malloc(rffi.CCHARP.TO, bufsize, flavor='raw') + res = widen(c_readlinkat(dir_fd, pathname, buf, bufsize)) + if res < 0: + lltype.free(buf, flavor='raw') + error = get_saved_errno() # failed + raise OSError(error, "readlinkat failed") + elif res < bufsize: + break # ok + else: + # buf too small, try again with a larger buffer + lltype.free(buf, flavor='raw') + bufsize *= 4 + # convert the result to a string + result = rffi.charp2strn(buf, res) + lltype.free(buf, flavor='raw') + return result + +if HAVE_RENAMEAT: + c_renameat = external( + 'renameat', + [rffi.INT, rffi.CCHARP, rffi.INT, rffi.CCHARP], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def renameat(src, dst, src_dir_fd=AT_FDCWD, dst_dir_fd=AT_FDCWD): + error = c_renameat(src_dir_fd, src, dst_dir_fd, dst) + handle_posix_error('renameat', error) + + +if HAVE_SYMLINKAT: + c_symlinkat = external('symlinkat', + [rffi.CCHARP, rffi.INT, rffi.CCHARP], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def symlinkat(src, dst, dir_fd=AT_FDCWD): + error = c_symlinkat(src, dir_fd, dst) + handle_posix_error('symlinkat', error) + +if HAVE_OPENAT: + c_openat = external('openat', + [rffi.INT, rffi.CCHARP, rffi.INT, rffi.MODE_T], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + @enforceargs(s_Str0, int, int, int, typecheck=False) + def openat(path, flags, mode, dir_fd=AT_FDCWD): + fd = c_openat(dir_fd, path, flags, mode) + return handle_posix_error('open', fd) + +if HAVE_MKFIFOAT: + c_mkfifoat = external('mkfifoat', + [rffi.INT, rffi.CCHARP, rffi.MODE_T], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def mkfifoat(path, mode, dir_fd=AT_FDCWD): + error = c_mkfifoat(dir_fd, path, mode) + handle_posix_error('mkfifoat', error) + +if HAVE_MKNODAT: + c_mknodat = external('mknodat', + [rffi.INT, rffi.CCHARP, rffi.MODE_T, rffi.INT], rffi.INT, + save_err=rffi.RFFI_SAVE_ERRNO) + + def mknodat(path, mode, device, dir_fd=AT_FDCWD): + error = c_mknodat(dir_fd, path, mode, device) + handle_posix_error('mknodat', error) diff --git a/rpython/rlib/test/test_rposix.py b/rpython/rlib/test/test_rposix.py --- a/rpython/rlib/test/test_rposix.py +++ b/rpython/rlib/test/test_rposix.py @@ -7,6 +7,12 @@ import errno import py +def rposix_requires(funcname): + return py.test.mark.skipif(not hasattr(rposix, funcname), + reason="Requires rposix.%s()" % funcname) + +win_only = py.test.mark.skipif("os.name != 'nt'") + class TestPosixFunction: def test_access(self): filename = str(udir.join('test_access.txt')) @@ -29,9 +35,8 @@ for value in times: assert isinstance(value, float) + @py.test.mark.skipif("not hasattr(os, 'getlogin')") def test_getlogin(self): - if not hasattr(os, 'getlogin'): - py.test.skip('posix specific function') try: expected = os.getlogin() except OSError, e: @@ -39,9 +44,8 @@ data = rposix.getlogin() assert data == expected + @win_only def test_utimes(self): - if os.name != 'nt': - py.test.skip('Windows specific feature') # Windows support centiseconds def f(fname, t1): os.utime(fname, (t1, t1)) @@ -51,15 +55,12 @@ t1 = 1159195039.25 compile(f, (str, float))(str(fname), t1) assert t1 == os.stat(str(fname)).st_mtime - if sys.version_info < (2, 7): - py.test.skip('requires Python 2.7') t1 = 5000000000.0 compile(f, (str, float))(str(fname), t1) assert t1 == os.stat(str(fname)).st_mtime + @win_only def test__getfullpathname(self): - if os.name != 'nt': - py.test.skip('nt specific function') posix = __import__(os.name) sysdrv = os.getenv('SystemDrive', 'C:') stuff = sysdrv + 'stuff' @@ -99,11 +100,25 @@ def test_mkdir(self): filename = str(udir.join('test_mkdir.dir')) rposix.mkdir(filename, 0) - exc = py.test.raises(OSError, rposix.mkdir, filename, 0) - assert exc.value.errno == errno.EEXIST + with py.test.raises(OSError) as excinfo: + rposix.mkdir(filename, 0) + assert excinfo.value.errno == errno.EEXIST if sys.platform == 'win32': assert exc.type is WindowsError + @rposix_requires('mkdirat') + def test_mkdirat(self): + relpath = 'test_mkdirat.dir' + filename = str(udir.join(relpath)) + dirfd = os.open(os.path.dirname(filename), os.O_RDONLY) + try: + rposix.mkdirat(relpath, 0, dir_fd=dirfd) + with py.test.raises(OSError) as excinfo: + rposix.mkdirat(relpath, 0, dir_fd=dirfd) + assert excinfo.value.errno == errno.EEXIST + finally: + os.close(dirfd) + def test_strerror(self): assert rposix.strerror(2) == os.strerror(2) @@ -116,10 +131,8 @@ os.unlink(filename) + @py.test.mark.skipif("os.name != 'posix'") def test_execve(self): - if os.name != 'posix': - py.test.skip('posix specific function') - EXECVE_ENV = {"foo": "bar", "baz": "quux"} def run_execve(program, args=None, env=None, do_path_lookup=False): @@ -258,11 +271,8 @@ assert rposix.isatty(-1) is False +@py.test.mark.skipif("not hasattr(os, 'ttyname')") class TestOsExpect(ExpectTest): - def setup_class(cls): - if not hasattr(os, 'ttyname'): - py.test.skip("no ttyname") - def test_ttyname(self): def f(): import os @@ -426,9 +436,8 @@ except Exception: pass + @win_only def test_is_valid_fd(self): - if os.name != 'nt': - py.test.skip('relevant for windows only') assert rposix.is_valid_fd(0) == 1 fid = open(str(udir.join('validate_test.txt')), 'w') fd = fid.fileno() @@ -448,6 +457,59 @@ def _get_filename(self): return str(udir.join('test_open_ascii')) + @rposix_requires('openat') + def test_openat(self): + def f(dirfd): + try: + fd = rposix.openat('test_open_ascii', os.O_RDONLY, 0777, dirfd) + try: + text = os.read(fd, 50) + return text + finally: + os.close(fd) + except OSError: + return '' + + dirfd = os.open(os.path.dirname(self.ufilename), os.O_RDONLY) + try: + assert ll_to_string(interpret(f, [dirfd])) == "test" + finally: + os.close(dirfd) + + @rposix_requires('unlinkat') + def test_unlinkat(self): + def f(dirfd): + return rposix.unlinkat('test_open_ascii', dir_fd=dirfd) + + dirfd = os.open(os.path.dirname(self.ufilename), os.O_RDONLY) + try: + interpret(f, [dirfd]) + finally: + os.close(dirfd) + assert not os.path.exists(self.ufilename) + + def test_utimensat(self): + def f(dirfd): + return rposix.utimensat('test_open_ascii', + 0, rposix.UTIME_NOW, 0, rposix.UTIME_NOW, dir_fd=dirfd) + + dirfd = os.open(os.path.dirname(self.ufilename), os.O_RDONLY) + try: + interpret(f, [dirfd]) # does not crash + finally: + os.close(dirfd) + + def test_fchmodat(self): + def f(dirfd): + return rposix.fchmodat('test_open_ascii', 0777, dirfd) + + dirfd = os.open(os.path.dirname(self.ufilename), os.O_RDONLY) + try: + interpret(f, [dirfd]) # does not crash + finally: + os.close(dirfd) + + class TestPosixUnicode(BasePosixUnicodeOrAscii): def _get_filename(self): return (unicode(udir.join('test_open')) + @@ -465,3 +527,30 @@ os.open('/tmp/t', 0, 0) os.open(u'/tmp/t', 0, 0) compile(f, ()) + + +def test_fdlistdir(tmpdir): + tmpdir.join('file').write('text') + dirfd = os.open(str(tmpdir), os.O_RDONLY) + result = rposix.fdlistdir(dirfd) + # Note: fdlistdir() always closes dirfd + assert result == ['file'] + +def test_symlinkat(tmpdir): + tmpdir.join('file').write('text') + dirfd = os.open(str(tmpdir), os.O_RDONLY) + try: + rposix.symlinkat('file', 'link', dir_fd=dirfd) + assert os.readlink(str(tmpdir.join('link'))) == 'file' + finally: + os.close(dirfd) + +def test_renameat(tmpdir): + tmpdir.join('file').write('text') + dirfd = os.open(str(tmpdir), os.O_RDONLY) + try: + rposix.renameat('file', 'file2', src_dir_fd=dirfd, dst_dir_fd=dirfd) + finally: + os.close(dirfd) + assert tmpdir.join('file').check(exists=False) + assert tmpdir.join('file2').check(exists=True) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit