Index: configure =================================================================== --- configure (revision 88367) +++ configure (working copy) @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.in Revision: 87698 . +# From configure.in Revision: 88350 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.68 for python 3.2. # @@ -6015,10 +6015,10 @@ unistd.h utime.h \ sys/audioio.h sys/bsdtty.h sys/epoll.h sys/event.h sys/file.h sys/loadavg.h \ sys/lock.h sys/mkdev.h sys/modem.h \ -sys/param.h sys/poll.h sys/select.h sys/socket.h sys/statvfs.h sys/stat.h \ -sys/termio.h sys/time.h \ -sys/times.h sys/types.h sys/un.h sys/utsname.h sys/wait.h pty.h libutil.h \ -sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \ +sys/param.h sys/poll.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \ +sys/stat.h sys/termio.h sys/time.h \ +sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \ +libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \ bluetooth/bluetooth.h linux/tipc.h spawn.h util.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` @@ -7685,6 +7685,51 @@ # checks for libraries +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sendfile in -lsendfile" >&5 +$as_echo_n "checking for sendfile in -lsendfile... " >&6; } +if ${ac_cv_lib_sendfile_sendfile+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsendfile $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char sendfile (); +int +main () +{ +return sendfile (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sendfile_sendfile=yes +else + ac_cv_lib_sendfile_sendfile=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sendfile_sendfile" >&5 +$as_echo "$ac_cv_lib_sendfile_sendfile" >&6; } +if test "x$ac_cv_lib_sendfile_sendfile" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSENDFILE 1 +_ACEOF + + LIBS="-lsendfile $LIBS" + +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 $as_echo_n "checking for dlopen in -ldl... " >&6; } if ${ac_cv_lib_dl_dlopen+:} false; then : @@ -9264,7 +9309,7 @@ initgroups kill killpg lchmod lchown lstat mbrtowc mkfifo mknod mktime \ mremap nice pathconf pause plock poll pthread_init \ putenv readlink realpath \ - select sem_open sem_timedwait sem_getvalue sem_unlink setegid seteuid \ + select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setuid setvbuf \ sigaction siginterrupt sigrelse snprintf strftime strlcpy \ Index: configure.in =================================================================== --- configure.in (revision 88367) +++ configure.in (working copy) @@ -1290,10 +1290,10 @@ unistd.h utime.h \ sys/audioio.h sys/bsdtty.h sys/epoll.h sys/event.h sys/file.h sys/loadavg.h \ sys/lock.h sys/mkdev.h sys/modem.h \ -sys/param.h sys/poll.h sys/select.h sys/socket.h sys/statvfs.h sys/stat.h \ -sys/termio.h sys/time.h \ -sys/times.h sys/types.h sys/un.h sys/utsname.h sys/wait.h pty.h libutil.h \ -sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \ +sys/param.h sys/poll.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \ +sys/stat.h sys/termio.h sys/time.h \ +sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \ +libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \ bluetooth/bluetooth.h linux/tipc.h spawn.h util.h) AC_HEADER_DIRENT AC_HEADER_MAJOR @@ -1892,6 +1892,7 @@ # checks for libraries +AC_CHECK_LIB(sendfile, sendfile) AC_CHECK_LIB(dl, dlopen) # Dynamic linking for SunOS/Solaris and SYSV AC_CHECK_LIB(dld, shl_load) # Dynamic linking for HP-UX @@ -2540,7 +2541,7 @@ initgroups kill killpg lchmod lchown lstat mbrtowc mkfifo mknod mktime \ mremap nice pathconf pause plock poll pthread_init \ putenv readlink realpath \ - select sem_open sem_timedwait sem_getvalue sem_unlink setegid seteuid \ + select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setuid setvbuf \ sigaction siginterrupt sigrelse snprintf strftime strlcpy \ Index: Doc/library/os.rst =================================================================== --- Doc/library/os.rst (revision 88367) +++ Doc/library/os.rst (working copy) @@ -775,6 +775,47 @@ :meth:`~file.readline` methods. +.. function:: sendfile(out, in, offset, nbytes) + sendfile(out, in, offset, nbytes, headers=None, trailers=None, flags=0) + + Copy *nbytes* bytes from file descriptor *in* to file descriptor *out*. + + The first case is supported by all platforms that define :func:`sendfile`. The + return value is a tuple, (byteswritten, offset), where offset is a value + pointing to the byte following the last byte read. + + On Linux, if *offset* is given as ``None``, the bytes are read from the + current position of *in* and the position of *in* is updated. It returns + the same as above with offset being ``None``. + + The second case may be used on Mac OS X and FreeBSD where *headers* and + *trailers* are arbitrary sequences of buffers that are written before and + after the data from *in* is written. It returns the same as the first case. + + On Mac OS X and FreeBSD, a value of 0 for *nbytes* specifies to send until + the end of *in* is reached. + + On Solaris, *out* may be the file descriptor of a regular file or the file + descriptor of a socket. On all other platforms, *out* must be the file + descriptor of an open socket. + + Availability: Unix. + + .. versionadded:: 3.3 + + +.. data:: SF_NODISKIO + SF_MNOWAIT + SF_SYNC + + Parameters to the :func:`sendfile` function, if the implementation supports + them. + + Availability: Unix. + + .. versionadded:: 3.3 + + .. function:: tcgetpgrp(fd) Return the process group associated with the terminal given by *fd* (an open Index: Lib/test/test_os.py =================================================================== --- Lib/test/test_os.py (revision 88367) +++ Lib/test/test_os.py (working copy) @@ -15,6 +15,13 @@ import contextlib import mmap import uuid +import asyncore +import asynchat +import socket +try: + import threading +except ImportError: + threading = None # Detect whether we're on a Linux system that uses the (now outdated # and unmaintained) linuxthreads threading library. There's an issue @@ -1261,6 +1268,276 @@ self.assertNotEqual(len(user_name), 0) +class SendfileTestServer(asyncore.dispatcher, threading.Thread): + + class Handler(asynchat.async_chat): + + def __init__(self, conn): + asynchat.async_chat.__init__(self, conn) + self.in_buffer = [] + self.closed = False + self.push(b"220 ready\r\n") + + def handle_read(self): + data = self.recv(4096) + self.in_buffer.append(data) + + def get_data(self): + return b''.join(self.in_buffer) + + def handle_close(self): + self.close() + self.closed = True + + def handle_error(self): + raise + + def __init__(self, address): + threading.Thread.__init__(self) + asyncore.dispatcher.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.bind(address) + self.listen(5) + self.host, self.port = self.socket.getsockname()[:2] + self.handler_instance = None + self._active = False + self._active_lock = threading.Lock() + + # --- public API + + @property + def running(self): + return self._active + + def start(self): + assert not self.running + self.__flag = threading.Event() + threading.Thread.start(self) + self.__flag.wait() + + def stop(self): + assert self.running + self._active = False + self.join() + + def wait(self): + # wait for handler connection to be closed, then stop the server + while not getattr(self.handler_instance, "closed", True): + pass + self.stop() + + # --- internals + + def run(self): + self._active = True + self.__flag.set() + while self._active and asyncore.socket_map: + self._active_lock.acquire() + asyncore.loop(timeout=0.001, count=1) + self._active_lock.release() + asyncore.close_all() + + def handle_accept(self): + conn, addr = self.accept() + self.handler_instance = self.Handler(conn) + + def handle_connect(self): + self.close() + handle_read = handle_connect + + def writable(self): + return 0 + + def handle_error(self): + raise + + +@unittest.skipUnless(hasattr(os, 'sendfile'), "test needs os.sendfile()") +class TestSendfile(unittest.TestCase): + + DATA = b"12345abcde" * 1024 * 1024 # 10 Mb + SUPPORT_HEADERS_TRAILERS = not sys.platform.startswith("linux") and \ + not sys.platform.startswith("solaris") + + @classmethod + def setUpClass(cls): + with open(support.TESTFN, "wb") as f: + f.write(cls.DATA) + + @classmethod + def tearDownClass(cls): + support.unlink(support.TESTFN) + + def setUp(self): + self.server = SendfileTestServer((support.HOST, 0)) + self.server.start() + self.client = socket.socket() + self.client.connect((self.server.host, self.server.port)) + self.client.settimeout(1) + # synchronize by waiting for "220 ready" response + self.client.recv(1024) + self.sockno = self.client.fileno() + self.file = open(support.TESTFN, 'rb') + self.fileno = self.file.fileno() + + def tearDown(self): + self.file.close() + self.client.close() + if self.server.running: + self.server.stop() + + def sendfile_wrapper(self, sock, file, offset, nbytes, headers=[], trailers=[]): + """A higher level wrapper representing how an application is + supposed to use sendfile(). + """ + while 1: + try: + if not self.SUPPORT_HEADERS_TRAILERS: + sent, new_offset = os.sendfile(sock, file, offset, nbytes, + headers, trailers) + else: + sent, new_offset = os.sendfile(sock, file, offset, nbytes) + except OSError as err: + if err.errno == errno.ECONNRESET: + # disconnected + raise + elif err.errno in (errno.EAGAIN, errno.EBUSY): + # we have to retry send data + continue + else: + raise + else: + self.assertTrue((new_offset - offset) <= sent) + return (sent, new_offset) + + def test_send_whole_file(self): + # normal send + total_sent = 0 + offset = 0 + nbytes = 4096 + while 1: + sent, offset = self.sendfile_wrapper(self.sockno, self.fileno, + offset, nbytes) + if sent == 0: + break + total_sent += sent + self.assertTrue(sent <= nbytes) + self.assertEqual(offset, total_sent) + + self.assertEqual(total_sent, len(self.DATA)) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(hash(data), hash(self.DATA)) + + def test_send_at_certain_offset(self): + # start sending a file at a certain offset + total_sent = 0 + offset = len(self.DATA) / 2 + nbytes = 4096 + while 1: + sent, offset = self.sendfile_wrapper(self.sockno, self.fileno, + offset, nbytes) + if sent == 0: + break + total_sent += sent + self.assertTrue(sent <= nbytes) + + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + expected = self.DATA[int(len(self.DATA) / 2):] + self.assertEqual(total_sent, len(expected)) + self.assertEqual(hash(data), hash(expected)) + + def test_offset_overflow(self): + # specify an offset > file size + offset = len(self.DATA) + 4096 + sent, new_offset = os.sendfile(self.sockno, self.fileno, offset, 4096) + self.assertEqual(sent, 0) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(data, b'') + + def test_invalid_offset(self): + with self.assertRaises(OSError) as cm: + os.sendfile(self.sockno, self.fileno, -1, 4096) + self.assertEqual(cm.exception.errno, errno.EINVAL) + + # --- headers / trailers tests + + if SUPPORT_HEADERS_TRAILERS: + + def test_headers(self): + total_sent = 0 + sent, offset = os.sendfile(self.sockno, self.fileno, 0, 4096, + headers=[b"x" * 512]) + total_sent += sent + offset = 4096 + nbytes = 4096 + while 1: + sent, offset = self.sendfile_wrapper(self.sockno, self.fileno, + offset, nbytes) + if sent == 0: + break + total_sent += sent + + expected_data = b"x" * 512 + self.DATA + self.assertEqual(total_sent, len(expected_data)) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(hash(data), hash(expected_data)) + + def test_trailers(self): + total_sent = 0 + sent, offset = os.sendfile(self.sockno, self.fileno, 0, 4096, + trailers=[b"x" * 512]) + total_sent += sent + offset = 4096 + nbytes = 4096 + while 1: + sent, offset = self.sendfile_wrapper(self.sockno, self.fileno, + offset, nbytes) + if sent == 0: + break + total_sent += sent + + expected_data = self.DATA[:4096] + b"x" * 512 + expected_data += self.DATA[4096:] + self.assertEqual(total_sent, len(expected_data)) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(hash(data), hash(expected_data)) + + def test_trailers(self): + TESTFN2 = support.TESTFN + "2" + f = open(TESTFN2, 'wb') + f.write(b"abcde") + f.close() + f = open(TESTFN2, 'rb') + try: + sent, offset = os.sendfile(self.sockno, f.fileno(), 0, 4096, + trailers=[b"12345"]) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(data, b"abcde12345") + finally: + os.remove(TESTFN2) + + if hasattr(os, "SF_NODISKIO"): + def test_flags(self): + try: + os.sendfile(self.sockno, self.fileno, 0, 4096, + flags=os.SF_NODISKIO) + except OSError as err: + if err.errno not in (errno.EBUSY, errno.EAGAIN): + raise + + def test_main(): support.run_unittest( FileTests, @@ -1281,6 +1558,7 @@ PidTests, LoginTests, LinkTests, + TestSendfile, ) if __name__ == "__main__": Index: Modules/posixmodule.c =================================================================== --- Modules/posixmodule.c (revision 88367) +++ Modules/posixmodule.c (working copy) @@ -95,6 +95,20 @@ #include #endif +#ifdef HAVE_SYS_SENDFILE_H +#include +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_SYS_UIO_H +#include +#endif +#endif + /* Various compilers have only certain posix functions */ /* XXX Gosh I wish these were all moved into pyconfig.h */ #if defined(PYCC_VACPP) && defined(PYOS_OS2) @@ -121,7 +135,7 @@ #ifdef _MSC_VER /* Microsoft compiler */ #define HAVE_GETCWD 1 #define HAVE_GETPPID 1 -#define HAVE_GETLOGIN 1 +#define HAVE_GETLOGIN 1 #define HAVE_SPAWNV 1 #define HAVE_EXECV 1 #define HAVE_PIPE 1 @@ -349,6 +363,20 @@ #endif #endif +int +PyParse_off_t(PyObject* arg, void* addr) +{ +#if !defined(HAVE_LARGEFILE_SUPPORT) + *((off_t*)addr) = PyLong_AsLong(arg); +#else + *((off_t*)addr) = PyLong_Check(arg) ? PyLong_AsLongLong(arg) + : PyLong_AsLong(arg); +#endif + if (PyErr_Occurred()) + return 0; + return 1; +} + #if defined _MSC_VER && _MSC_VER >= 1400 /* Microsoft CRT in VS2005 and higher will verify that a filehandle is * valid and throw an assertion if it isn't. @@ -1283,7 +1311,7 @@ The _w represent Unicode equivalents of the aformentioned ANSI functions. */ -static int +static int win32_lstat(const char* path, struct win32_stat *result) { return win32_xstat(path, result, FALSE); @@ -1301,7 +1329,7 @@ return win32_xstat(path, result, TRUE); } -static int +static int win32_stat_w(const wchar_t* path, struct win32_stat *result) { return win32_xstat_w(path, result, TRUE); @@ -2339,7 +2367,7 @@ if (PyArg_ParseTuple(args, "|U:listdir", &po)) { WIN32_FIND_DATAW wFileData; Py_UNICODE *wnamebuf, *po_wchars; - + if (po == NULL) { /* Default arg: "." */ po_wchars = L"."; len = 1; @@ -2741,7 +2769,7 @@ int result_length; PyObject *result; wchar_t *path; - + if (!PyArg_ParseTuple(args, "u|:_getfinalpathname", &path)) { return NULL; } @@ -2762,7 +2790,7 @@ /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ FILE_FLAG_BACKUP_SEMANTICS, NULL); - + if(hFile == INVALID_HANDLE_VALUE) { return win32_error_unicode("GetFinalPathNamyByHandle", path); return PyErr_Format(PyExc_RuntimeError, @@ -3072,7 +3100,7 @@ if (GetFileAttributesExW(lpFileName, GetFileExInfoStandard, &info)) { is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; - + /* Get WIN32_FIND_DATA structure for the path to determine if it is a symlink */ if(is_directory && @@ -4262,7 +4290,7 @@ #endif gid_t grouplist[MAX_GROUPS]; - /* On MacOSX getgroups(2) can return more than MAX_GROUPS results + /* On MacOSX getgroups(2) can return more than MAX_GROUPS results * This is a helper variable to store the intermediate result when * that happens. * @@ -4478,15 +4506,15 @@ posix_getlogin(PyObject *self, PyObject *noargs) { PyObject *result = NULL; -#ifdef MS_WINDOWS +#ifdef MS_WINDOWS wchar_t user_name[UNLEN + 1]; DWORD num_chars = sizeof(user_name)/sizeof(user_name[0]); if (GetUserNameW(user_name, &num_chars)) { /* num_chars is the number of unicode chars plus null terminator */ result = PyUnicode_FromWideChar(user_name, num_chars - 1); - } - else + } + else result = PyErr_SetFromWindowsErr(GetLastError()); #else char *name; @@ -5170,12 +5198,12 @@ FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0); Py_END_ALLOW_THREADS - + if (reparse_point_handle==INVALID_HANDLE_VALUE) { return win32_error_unicode("readlink", path); } - + Py_BEGIN_ALLOW_THREADS /* New call DeviceIoControl to read the reparse point */ io_result = DeviceIoControl( @@ -5246,7 +5274,7 @@ int target_is_directory = 0; DWORD res; WIN32_FILE_ATTRIBUTE_DATA src_info; - + if (!check_CreateSymbolicLinkW()) { /* raise NotImplementedError */ @@ -5265,7 +5293,7 @@ Py_DECREF(src); return NULL; } - + /* if src is a directory, ensure target_is_directory==1 */ if( GetFileAttributesExW( @@ -5288,7 +5316,7 @@ { return win32_error_unicode("symlink", PyUnicode_AsUnicode(src)); } - + Py_INCREF(Py_None); return Py_None; } @@ -5723,6 +5751,183 @@ return PyLong_FromSsize_t(size); } +#ifdef HAVE_SENDFILE +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) +static int +iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, int cnt, int type) +{ + int i, j; + *iov = PyMem_New(struct iovec, cnt); + if (*iov == NULL) { + PyErr_NoMemory(); + return 0; + } + *buf = PyMem_New(Py_buffer, cnt); + if (*buf == NULL) { + PyMem_Del(*iov); + PyErr_NoMemory(); + return 0; + } + + for (i = 0; i < cnt; i++) { + if (PyObject_GetBuffer(PySequence_GetItem(seq, i), &(*buf)[i], + type) == -1) { + PyMem_Del(*iov); + for (j = 0; j < i; j++) { + PyBuffer_Release(&(*buf)[j]); + } + PyMem_Del(*buf); + return 0; + } + (*iov)[i].iov_base = (*buf)[i].buf; + (*iov)[i].iov_len = (*buf)[i].len; + } + return 1; +} + +static void +iov_cleanup(struct iovec *iov, Py_buffer *buf, int cnt) +{ + int i; + PyMem_Del(iov); + for (i = 0; i < cnt; i++) { + PyBuffer_Release(&buf[i]); + } + PyMem_Del(buf); +} +#endif + +PyDoc_STRVAR(posix_sendfile__doc__, +"sendfile(out, in, offset, nbytes) -> (byteswritten, offset)\n\ +sendfile(out, in, offset, nbytes, headers=None, trailers=None, flags=0)\n\ + -> (byteswritten, offset)\n\ +Copy nbytes bytes from file descriptor in to file descriptor out."); + +static PyObject * +posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) +{ + int in, out; + Py_ssize_t ret; + off_t offset; + +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) +#ifndef __APPLE__ + Py_ssize_t len; +#endif + PyObject *headers = NULL, *trailers = NULL; + Py_buffer *hbuf, *tbuf; + off_t sbytes; + struct sf_hdtr sf; + int flags = 0; + sf.headers = NULL; + sf.trailers = NULL; + static char *keywords[] = {"out", "in", + "offset", "count", + "headers", "trailers", "flags", NULL}; + +#ifdef __APPLE__ + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iiO&O&|OOi:sendfile", + keywords, &out, &in, PyParse_off_t, &offset, PyParse_off_t, &sbytes, +#else + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iiO&n|OOi:sendfile", + keywords, &out, &in, PyParse_off_t, &offset, &len, +#endif + &headers, &trailers, &flags)) + return NULL; + if (headers != NULL) { + if (!PySequence_Check(headers)) { + PyErr_SetString(PyExc_TypeError, + "sendfile() headers must be a sequence or None"); + return NULL; + } else { + sf.hdr_cnt = PySequence_Size(headers); + if (sf.hdr_cnt > 0 && !iov_setup(&(sf.headers), &hbuf, + headers, sf.hdr_cnt, PyBUF_SIMPLE)) + return NULL; + } + } + if (trailers != NULL) { + if (!PySequence_Check(trailers)) { + PyErr_SetString(PyExc_TypeError, + "sendfile() trailers must be a sequence or None"); + return NULL; + } else { + sf.trl_cnt = PySequence_Size(trailers); + if (sf.trl_cnt > 0 && !iov_setup(&(sf.trailers), &tbuf, + trailers, sf.trl_cnt, PyBUF_SIMPLE)) + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS +#ifdef __APPLE__ + ret = sendfile(in, out, offset, &sbytes, &sf, flags); +#else + ret = sendfile(in, out, offset, len, &sf, &sbytes, flags); +#endif + Py_END_ALLOW_THREADS + + if (sf.headers != NULL) + iov_cleanup(sf.headers, hbuf, sf.hdr_cnt); + if (sf.trailers != NULL) + iov_cleanup(sf.trailers, tbuf, sf.trl_cnt); + + if (ret < 0) { + if ((errno == EAGAIN) || (errno == EBUSY)) { + if (sbytes != 0) { + // some data has been sent + goto done; + } + else { + // no data has been sent; upper application is supposed + // to retry on EAGAIN or EBUSY + return posix_error(); + } + } + return posix_error(); + } + goto done; + +done: + #if !defined(HAVE_LARGEFILE_SUPPORT) + return Py_BuildValue("ll", sbytes, offset + sbytes); + #else + return Py_BuildValue("LL", sbytes, offset + sbytes); + #endif + +#else + Py_ssize_t count; + PyObject *offobj; + static char *keywords[] = {"out", "in", + "offset", "count", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iiOn:sendfile", + keywords, &out, &in, &offobj, &count)) + return NULL; +#ifdef linux + if (offobj == Py_None) { + Py_BEGIN_ALLOW_THREADS + ret = sendfile(out, in, NULL, count); + Py_END_ALLOW_THREADS + if (ret < 0) + return posix_error(); + Py_INCREF(Py_None); + return Py_BuildValue("nO", ret, Py_None); + } +#endif + PyParse_off_t(offobj, &offset); + Py_BEGIN_ALLOW_THREADS + ret = sendfile(out, in, &offset, count); + Py_END_ALLOW_THREADS + if (ret < 0) + return posix_error(); +#if !defined(HAVE_LARGEFILE_SUPPORT) + return Py_BuildValue("nl", ret, offset); +#else + return Py_BuildValue("nL", ret, offset); +#endif +#endif +} +#endif PyDoc_STRVAR(posix_fstat__doc__, "fstat(fd) -> stat result\n\n\ @@ -7971,6 +8176,10 @@ {"lseek", posix_lseek, METH_VARARGS, posix_lseek__doc__}, {"read", posix_read, METH_VARARGS, posix_read__doc__}, {"write", posix_write, METH_VARARGS, posix_write__doc__}, +#ifdef HAVE_SENDFILE + {"sendfile", (PyCFunction)posix_sendfile, METH_VARARGS | METH_KEYWORDS, + posix_sendfile__doc__}, +#endif {"fstat", posix_fstat, METH_VARARGS, posix_fstat__doc__}, {"isatty", posix_isatty, METH_VARARGS, posix_isatty__doc__}, #ifdef HAVE_PIPE @@ -8362,6 +8571,17 @@ if (ins(d, "ST_NOSUID", (long)ST_NOSUID)) return -1; #endif /* ST_NOSUID */ + /* FreeBSD sendfile() constants */ +#ifdef SF_NODISKIO + if (ins(d, "SF_NODISKIO", (long)SF_NODISKIO)) return -1; +#endif +#ifdef SF_MNOWAIT + if (ins(d, "SF_MNOWAIT", (long)SF_MNOWAIT)) return -1; +#endif +#ifdef SF_SYNC + if (ins(d, "SF_SYNC", (long)SF_SYNC)) return -1; +#endif + #ifdef HAVE_SPAWNV #if defined(PYOS_OS2) && defined(PYCC_GCC) if (ins(d, "P_WAIT", (long)P_WAIT)) return -1; Index: pyconfig.h.in =================================================================== --- pyconfig.h.in (revision 88367) +++ pyconfig.h.in (working copy) @@ -1024,6 +1024,9 @@ /* The size of `size_t', as computed by sizeof. */ #undef SIZEOF_SIZE_T +/* Define to 1 if you have the `sendfile' function. */ +#undef HAVE_SENDFILE + /* The size of `time_t', as computed by sizeof. */ #undef SIZEOF_TIME_T