From 5ebfd36afa9093099ab23e4d1256274ae7ee6978 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Tue, 13 Nov 2001 23:11:19 +0000 Subject: CVS patch #477161: New "access" keyword for mmap, from Jay T Miller. This gives mmap() on Windows the ability to create read-only, write- through and copy-on-write mmaps. A new keyword argument is introduced because the mmap() signatures diverged between Windows and Unix, so while they (now) both support this functionality, there wasn't a way to spell it in a common way without introducing a new spelling gimmick. The old spellings are still accepted, so there isn't a backward- compatibility issue here. --- Doc/lib/libmmap.tex | 162 ++++++++++++++++++++------------ Lib/test/output/test_mmap | 13 +++ Lib/test/test_mmap.py | 138 ++++++++++++++++++++++++--- Misc/NEWS | 9 +- Modules/mmapmodule.c | 235 ++++++++++++++++++++++++++++++++-------------- 5 files changed, 412 insertions(+), 145 deletions(-) diff --git a/Doc/lib/libmmap.tex b/Doc/lib/libmmap.tex index 0bd0332..917635b 100644 --- a/Doc/lib/libmmap.tex +++ b/Doc/lib/libmmap.tex @@ -1,5 +1,5 @@ \section{\module{mmap} --- - Memory-mapped file support} +Memory-mapped file support} \declaremodule{builtin}{mmap} \modulesynopsis{Interface to memory-mapped files for Unix and Windows.} @@ -23,36 +23,67 @@ If you wish to map an existing Python file object, use its \function{os.open()} function, which returns a file descriptor directly (the file still needs to be closed when done). -\begin{funcdesc}{mmap}{fileno, length\optional{, tagname}} -\strong{(Windows version)} Maps \var{length} bytes from the file -specified by the file handle \var{fileno}, and returns a mmap object. -If \var{length} is \code{0}, the maximum length of the map will be the -current size of the file when \function{mmap()} is called. - -\var{tagname}, if specified and not \code{None}, is a string giving a -tag name for the mapping. Windows allows you to have many different -mappings against the same file. If you specify the name of an -existing tag, that tag is opened, otherwise a new tag of this name is -created. If this parameter is omitted or \code{None}, the mapping is -created without a name. Avoiding the use of the tag parameter will -assist in keeping your code portable between \UNIX{} and Windows. +\begin{funcdesc}{mmap}{fileno, length\optional{, tagname\optional{, access}}} + \strong{(Windows version)} Maps \var{length} bytes from the file + specified by the file handle \var{fileno}, and returns a mmap + object. If \var{length} is \code{0}, the maximum length of the map + will be the current size of the file when \function{mmap()} is + called. + + \var{tagname}, if specified and not \code{None}, is a string giving + a tag name for the mapping. Windows allows you to have many + different mappings against the same file. If you specify the name + of an existing tag, that tag is opened, otherwise a new tag of this + name is created. If this parameter is omitted or \code{None}, the + mapping is created without a name. Avoiding the use of the tag + parameter will assist in keeping your code portable between \UNIX{} + and Windows. + + \var{access} may be specified as an optional keyword parameter. + \var{access} accepts one of three values: \constant{ACCESS_READ}, + \constant{ACCESS_WRITE}, or \constant{ACCESS_COPY} to specify + readonly, write-through or copy-on-write memory respectively. + \var{access} can be used on both \UNIX{} and Windows. If + \var{access} is not specified, Windows mmap returns a write-through + mapping. The initial memory values for all three access types are + taken from the specified file. Assignment to an + \constant{ACCESS_READ} memory map raises a \exception{TypeError} + exception. Assignment to an \constant{ACCESS_WRITE} memory map + affects both memory and the underlying file. Assigment to an + \constant{ACCESS_COPY} memory map affects memory but does not update + the underlying file. \end{funcdesc} -\begin{funcdesc}{mmap}{fileno, length\optional{, flags\optional{, prot}}} -\strong{(\UNIX{} version)} Maps \var{length} bytes from the file -specified by the file descriptor \var{fileno}, and returns a mmap object. - -\var{flags} specifies the nature of the mapping. -\constant{MAP_PRIVATE} creates a private copy-on-write mapping, so -changes to the contents of the mmap object will be private to this -process, and \constant{MAP_SHARED} creates a mapping that's shared -with all other processes mapping the same areas of the file. -The default value is \constant{MAP_SHARED}. - -\var{prot}, if specified, gives the desired memory protection; the two -most useful values are \constant{PROT_READ} and \constant{PROT_WRITE}, -to specify that the pages may be read or written. -\var{prot} defaults to \constant{PROT_READ | PROT_WRITE}. +\begin{funcdesc}{mmap}{fileno, length\optional{, flags\optional{, prot\optional{, access}}}} + \strong{(\UNIX{} version)} Maps \var{length} bytes from the file + specified by the file descriptor \var{fileno}, and returns a mmap + object. + + \var{flags} specifies the nature of the mapping. + \constant{MAP_PRIVATE} creates a private copy-on-write mapping, so + changes to the contents of the mmap object will be private to this + process, and \constant{MAP_SHARED} creates a mapping that's shared + with all other processes mapping the same areas of the file. The + default value is \constant{MAP_SHARED}. + + \var{prot}, if specified, gives the desired memory protection; the + two most useful values are \constant{PROT_READ} and + \constant{PROT_WRITE}, to specify that the pages may be read or + written. \var{prot} defaults to \constant{PROT_READ | PROT_WRITE}. + + \var{access} may be specified in lieu of \var{flags} and \var{prot} + as an optional keyword parameter. \var{access} accepts one of three + values: \constant{ACCESS_READ}, \constant{ACCESS_WRITE}, or + \constant{ACCESS_COPY} to specify readonly, write-through, or + copy-on-write memory respectively. \var{access} can be used on both + \UNIX{} and Windows. It is an error to specify both \var{flags}, + \var{prot} and \var{access}. The initial memory values for all + three access types are taken from the specified file. Assignment to + an \constant{ACCESS_READ} memory map raises a \exception{TypeError} + exception. Assignment to an \constant{ACCESS_WRITE} memory map + affects both memory and the underlying file. Assigment to an + \constant{ACCESS_COPY} memory map affects memory but does not update + the underlying file. \end{funcdesc} @@ -60,73 +91,80 @@ Memory-mapped file objects support the following methods: \begin{methoddesc}{close}{} -Close the file. Subsequent calls to other methods of the object -will result in an exception being raised. + Close the file. Subsequent calls to other methods of the object + will result in an exception being raised. \end{methoddesc} \begin{methoddesc}{find}{string\optional{, start}} -Returns the lowest index in the object where the substring -\var{string} is found. Returns \code{-1} on failure. \var{start} is -the index at which the search begins, and defaults to zero. + Returns the lowest index in the object where the substring + \var{string} is found. Returns \code{-1} on failure. \var{start} + is the index at which the search begins, and defaults to zero. \end{methoddesc} \begin{methoddesc}{flush}{\optional{offset, size}} -Flushes changes made to the in-memory copy of a file back to disk. -Without use of this call there is no guarantee that changes are -written back before the object is destroyed. If \var{offset} and -\var{size} are specified, only changes to the given range of bytes -will be flushed to disk; otherwise, the whole extent of the mapping is -flushed. + Flushes changes made to the in-memory copy of a file back to disk. + Without use of this call there is no guarantee that changes are + written back before the object is destroyed. If \var{offset} and + \var{size} are specified, only changes to the given range of bytes + will be flushed to disk; otherwise, the whole extent of the mapping + is flushed. \end{methoddesc} \begin{methoddesc}{move}{\var{dest}, \var{src}, \var{count}} -Copy the \var{count} bytes starting at offset \var{src} -to the destination index \var{dest}. + Copy the \var{count} bytes starting at offset \var{src} to the + destination index \var{dest}. If the mmap was created with + \constant{ACCESS_READ}, then calls to move will throw a + \exception{TypeError} exception. \end{methoddesc} \begin{methoddesc}{read}{\var{num}} -Return a string containing up to \var{num} bytes starting from the -current file position; the file position is updated to point after the -bytes that were returned. + Return a string containing up to \var{num} bytes starting from the + current file position; the file position is updated to point after the + bytes that were returned. \end{methoddesc} \begin{methoddesc}{read_byte}{} -Returns a string of length 1 containing the character at the current -file position, and advances the file position by 1. + Returns a string of length 1 containing the character at the current + file position, and advances the file position by 1. \end{methoddesc} \begin{methoddesc}{readline}{} -Returns a single line, starting at the current file position and up to -the next newline. + Returns a single line, starting at the current file position and up to + the next newline. \end{methoddesc} \begin{methoddesc}{resize}{\var{newsize}} + If the mmap was created with \constant{ACCESS_READ} or + \constant{ACCESS_COPY}, resizing the map will throw a \exception{TypeError} exception. \end{methoddesc} \begin{methoddesc}{seek}{pos\optional{, whence}} -Set the file's current position. -\var{whence} argument is optional and defaults to \code{0} (absolute -file positioning); other values are \code{1} (seek relative to the -current position) and \code{2} (seek relative to the file's end). + Set the file's current position. \var{whence} argument is optional + and defaults to \code{0} (absolute file positioning); other values + are \code{1} (seek relative to the current position) and \code{2} + (seek relative to the file's end). \end{methoddesc} \begin{methoddesc}{size}{} -Return the length of the file, which can be larger than the size -of the memory-mapped area. + Return the length of the file, which can be larger than the size of + the memory-mapped area. \end{methoddesc} \begin{methoddesc}{tell}{} -Returns the current position of the file pointer. + Returns the current position of the file pointer. \end{methoddesc} \begin{methoddesc}{write}{\var{string}} -Write the bytes in \var{string} into memory at the current position of -the file pointer; the file position is updated to point after the -bytes that were written. + Write the bytes in \var{string} into memory at the current position + of the file pointer; the file position is updated to point after the + bytes that were written. If the mmap was created with + \constant{ACCESS_READ}, then writing to it will throw a + \exception{TypeError} exception. \end{methoddesc} \begin{methoddesc}{write_byte}{\var{byte}} -Write the single-character string \var{byte} into memory at the -current position of the file pointer; the file position is advanced by -\code{1}. + Write the single-character string \var{byte} into memory at the + current position of the file pointer; the file position is advanced + by \code{1}.If the mmap was created with \constant{ACCESS_READ}, + then writing to it will throw a \exception{TypeError} exception. \end{methoddesc} diff --git a/Lib/test/output/test_mmap b/Lib/test/output/test_mmap index 815cfe3..f1a25a9 100644 --- a/Lib/test/output/test_mmap +++ b/Lib/test/output/test_mmap @@ -17,4 +17,17 @@ test_mmap Try to seek beyond end of mmap... Try to seek to negative position... Attempting resize() + Creating 10 byte test data file. + Opening mmap with access=ACCESS_READ + Ensuring that readonly mmap can't be slice assigned. + Ensuring that readonly mmap can't be item assigned. + Ensuring that readonly mmap can't be write() to. + Ensuring that readonly mmap can't be write_byte() to. + Ensuring that readonly mmap can't be resized. + Opening mmap with access=ACCESS_WRITE + Modifying write-through memory map. + Opening mmap with access=ACCESS_COPY + Modifying copy-on-write memory map. + Ensuring copy-on-write maps cannot be resized. + Ensuring invalid access parameter raises exception. Test passed diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index eb31dc3..6bb974e 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1,4 +1,4 @@ -from test_support import verify, TESTFN +from test_support import verify, vereq, TESTFN import mmap import os, re @@ -25,15 +25,15 @@ def test_both(): print type(m) # SF bug 128713: segfaulted on Linux print ' Position of foo:', m.find('foo') / float(PAGESIZE), 'pages' - verify(m.find('foo') == PAGESIZE) + vereq(m.find('foo'), PAGESIZE) print ' Length of file:', len(m) / float(PAGESIZE), 'pages' - verify(len(m) == 2*PAGESIZE) + vereq(len(m), 2*PAGESIZE) print ' Contents of byte 0:', repr(m[0]) - verify(m[0] == '\0') + vereq(m[0], '\0') print ' Contents of first 3 bytes:', repr(m[0:3]) - verify(m[0:3] == '\0\0\0') + vereq(m[0:3], '\0\0\0') # Modify the file's content print "\n Modifying file's content..." @@ -42,11 +42,11 @@ def test_both(): # Check that the modification worked print ' Contents of byte 0:', repr(m[0]) - verify(m[0] == '3') + vereq(m[0], '3') print ' Contents of first 3 bytes:', repr(m[0:3]) - verify(m[0:3] == '3\0\0') + vereq(m[0:3], '3\0\0') print ' Contents of second page:', repr(m[PAGESIZE-1 : PAGESIZE + 7]) - verify(m[PAGESIZE-1 : PAGESIZE + 7] == '\0foobar\0') + vereq(m[PAGESIZE-1 : PAGESIZE + 7], '\0foobar\0') m.flush() @@ -61,19 +61,19 @@ def test_both(): print ' Regex match on mmap (page start, length of match):', print start / float(PAGESIZE), length - verify(start == PAGESIZE) - verify(end == PAGESIZE + 6) + vereq(start, PAGESIZE) + vereq(end, PAGESIZE + 6) # test seeking around (try to overflow the seek implementation) m.seek(0,0) print ' Seek to zeroth byte' - verify(m.tell() == 0) + vereq(m.tell(), 0) m.seek(42,1) print ' Seek to 42nd byte' - verify(m.tell() == 42) + vereq(m.tell(), 42) m.seek(0,2) print ' Seek to last byte' - verify(m.tell() == len(m)) + vereq(m.tell(), len(m)) print ' Try to seek to negative position...' try: @@ -132,6 +132,118 @@ def test_both(): except OSError: pass + # Test for "access" keyword parameter + try: + mapsize = 10 + print " Creating", mapsize, "byte test data file." + open(TESTFN, "wb").write("a"*mapsize) + print " Opening mmap with access=ACCESS_READ" + f = open(TESTFN, "rb") + m = mmap.mmap(f.fileno(), mapsize, access=mmap.ACCESS_READ) + verify(m[:] == 'a'*mapsize, "Readonly memory map data incorrect.") + + print " Ensuring that readonly mmap can't be slice assigned." + try: + m[:] = 'b'*mapsize + except TypeError: + pass + else: + verify(0, "Able to write to readonly memory map") + + print " Ensuring that readonly mmap can't be item assigned." + try: + m[0] = 'b' + except TypeError: + pass + else: + verify(0, "Able to write to readonly memory map") + + print " Ensuring that readonly mmap can't be write() to." + try: + m.seek(0,0) + m.write('abc') + except TypeError: + pass + else: + verify(0, "Able to write to readonly memory map") + + print " Ensuring that readonly mmap can't be write_byte() to." + try: + m.seek(0,0) + m.write_byte('d') + except TypeError: + pass + else: + verify(0, "Able to write to readonly memory map") + + print " Ensuring that readonly mmap can't be resized." + try: + m.resize(2*mapsize) + except SystemError: # resize is not universally supported + pass + except TypeError: + pass + else: + verify(0, "Able to resize readonly memory map") + del m, f + verify(open(TESTFN, "rb").read() == 'a'*mapsize, + "Readonly memory map data file was modified") + + print " Opening mmap with access=ACCESS_WRITE" + f = open(TESTFN, "r+b") + m = mmap.mmap(f.fileno(), mapsize, access=mmap.ACCESS_WRITE) + print " Modifying write-through memory map." + m[:] = 'c'*mapsize + verify(m[:] == 'c'*mapsize, + "Write-through memory map memory not updated properly.") + m.flush() + del m, f + verify(open(TESTFN).read() == 'c'*mapsize, + "Write-through memory map data file not updated properly.") + + print " Opening mmap with access=ACCESS_COPY" + f = open(TESTFN, "r+b") + m = mmap.mmap(f.fileno(), mapsize, access=mmap.ACCESS_COPY) + print " Modifying copy-on-write memory map." + m[:] = 'd'*mapsize + verify(m[:] == 'd' * mapsize, + "Copy-on-write memory map data not written correctly.") + m.flush() + verify(open(TESTFN, "rb").read() == 'c'*mapsize, + "Copy-on-write test data file should not be modified.") + try: + print " Ensuring copy-on-write maps cannot be resized." + m.resize(2*mapsize) + except TypeError: + pass + else: + verify(0, "Copy-on-write mmap resize did not raise exception.") + del m, f + try: + print " Ensuring invalid access parameter raises exception." + f = open(TESTFN, "r+b") + m = mmap.mmap(f.fileno(), mapsize, access=4) + except ValueError: + pass + else: + verify(0, "Invalid access code should have raised exception.") + + if os.name == "posix": + print " Trying incompatible flags, prot and access parameters." + f=open(TESTFN, "r+b") + try: + m = mmap.mmap(f.fileno(), mapsize, flags=mmap.MAP_PRIVATE, + prot=mmap.PROT_READ, access=mmap.ACCESS_WRITE) + except ValueError: + pass + else: + verify(0, "Incompatible parameters should raise ValueError.") + finally: + try: + os.unlink(TESTFN) + except OSError: + pass + print ' Test passed' test_both() diff --git a/Misc/NEWS b/Misc/NEWS index ba98321..763a114 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -36,6 +36,13 @@ Core and builtins Extension modules +- mmap has a new keyword argument, "access", allowing a uniform way for + both Windows and Unix users to create read-only, write-through and + copy-on-write memory mappings. This was previously possible only on + Unix. A new keyword argument was required to support this in a + uniform way because the mmap() signuatures had diverged across + platforms. Thanks to Jay T Miller for repairing this! + - By default, the gc.garbage list now contains only those instances in unreachable cycles that have __del__ methods; in 2.1 it contained all instances in unreachable cycles. "Instances" here has been generalized @@ -55,7 +62,7 @@ Extension modules Library -- tkFileDialog exposes a Directory class and askdirectory +- tkFileDialog exposes a Directory class and askdirectory convenience function. - Symbolic group names in regular expressions must be unique. For diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 8d57e90..a4ec2d0 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -29,9 +29,9 @@ static int my_getpagesize(void) { - SYSTEM_INFO si; - GetSystemInfo(&si); - return si.dwPageSize; + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; } #endif @@ -49,7 +49,7 @@ my_getpagesize(void) static int my_getpagesize(void) { - return sysconf(_SC_PAGESIZE); + return sysconf(_SC_PAGESIZE); } #else #define my_getpagesize getpagesize @@ -62,6 +62,14 @@ my_getpagesize(void) static PyObject *mmap_module_error; +typedef enum +{ + ACCESS_DEFAULT, + ACCESS_READ, + ACCESS_WRITE, + ACCESS_COPY +} access_mode; + typedef struct { PyObject_HEAD char * data; @@ -77,8 +85,11 @@ typedef struct { #ifdef UNIX int fd; #endif + + access_mode access; } mmap_object; + static void mmap_object_dealloc(mmap_object *m_obj) { @@ -178,7 +189,7 @@ mmap_read_byte_method(mmap_object *self, static PyObject * mmap_read_line_method(mmap_object *self, - PyObject *args) + PyObject *args) { char *start = self->data+self->pos; char *eof = self->data+self->size; @@ -236,11 +247,11 @@ mmap_find_method(mmap_object *self, char *e = self->data + self->size; if (start < 0) - start += self->size; + start += self->size; if (start < 0) - start = 0; + start = 0; else if ((size_t)start > self->size) - start = self->size; + start = self->size; p = self->data + start; while (p < e) { @@ -260,6 +271,26 @@ mmap_find_method(mmap_object *self, } } +static int +is_writeable(mmap_object *self) +{ + if (self->access != ACCESS_READ) + return 1; + PyErr_Format(PyExc_TypeError, "mmap can't modify a readonly memory map."); + return 0; +} + +static int +is_resizeable(mmap_object *self) +{ + if ((self->access == ACCESS_WRITE) || (self->access == ACCESS_DEFAULT)) + return 1; + PyErr_Format(PyExc_TypeError, + "mmap can't resize a readonly or copy-on-write memory map."); + return 0; +} + + static PyObject * mmap_write_method(mmap_object *self, PyObject *args) @@ -271,6 +302,9 @@ mmap_write_method(mmap_object *self, if (!PyArg_ParseTuple (args, "s#:write", &data, &length)) return(NULL); + if (!is_writeable(self)) + return NULL; + if ((self->pos + length) > self->size) { PyErr_SetString (PyExc_ValueError, "data out of range"); return NULL; @@ -291,12 +325,14 @@ mmap_write_byte_method(mmap_object *self, if (!PyArg_ParseTuple (args, "c:write_byte", &value)) return(NULL); + if (!is_writeable(self)) + return NULL; *(self->data+self->pos) = value; self->pos += 1; Py_INCREF (Py_None); return (Py_None); } - + static PyObject * mmap_size_method(mmap_object *self, PyObject *args) @@ -342,7 +378,8 @@ mmap_resize_method(mmap_object *self, { unsigned long new_size; CHECK_VALID(NULL); - if (!PyArg_ParseTuple (args, "l:resize", &new_size)) { + if (!PyArg_ParseTuple (args, "l:resize", &new_size) || + !is_resizeable(self)) { return NULL; #ifdef MS_WIN32 } else { @@ -386,31 +423,31 @@ mmap_resize_method(mmap_object *self, #ifdef UNIX #ifndef HAVE_MREMAP -} else { - PyErr_SetString(PyExc_SystemError, - "mmap: resizing not available--no mremap()"); - return NULL; + } else { + PyErr_SetString(PyExc_SystemError, + "mmap: resizing not available--no mremap()"); + return NULL; #else -} else { - void *newmap; + } else { + void *newmap; #ifdef MREMAP_MAYMOVE - newmap = mremap(self->data, self->size, new_size, MREMAP_MAYMOVE); + newmap = mremap(self->data, self->size, new_size, MREMAP_MAYMOVE); #else - newmap = mremap(self->data, self->size, new_size, 0); + newmap = mremap(self->data, self->size, new_size, 0); #endif - if (newmap == (void *)-1) - { - PyErr_SetFromErrno(mmap_module_error); - return NULL; - } - self->data = newmap; - self->size = new_size; - Py_INCREF(Py_None); - return Py_None; + if (newmap == (void *)-1) + { + PyErr_SetFromErrno(mmap_module_error); + return NULL; + } + self->data = newmap; + self->size = new_size; + Py_INCREF(Py_None); + return Py_None; #endif /* HAVE_MREMAP */ #endif /* UNIX */ -} + } } static PyObject * @@ -491,7 +528,7 @@ mmap_seek_method(mmap_object *self, PyObject *args) return (Py_None); } -onoutofrange: + onoutofrange: PyErr_SetString (PyExc_ValueError, "seek out of range"); return NULL; } @@ -501,7 +538,8 @@ mmap_move_method(mmap_object *self, PyObject *args) { unsigned long dest, src, count; CHECK_VALID(NULL); - if (!PyArg_ParseTuple (args, "iii:move", &dest, &src, &count)) { + if (!PyArg_ParseTuple (args, "iii:move", &dest, &src, &count) || + !is_writeable(self)) { return NULL; } else { /* bounds check the values */ @@ -561,6 +599,8 @@ mmap_buffer_getwritebuf(mmap_object *self, int index, const void **ptr) "Accessing non-existent mmap segment"); return -1; } + if (!is_writeable(self)) + return -1; *ptr = self->data; return self->size; } @@ -665,7 +705,7 @@ mmap_ass_slice(mmap_object *self, int ilow, int ihigh, PyObject *v) if (v == NULL) { PyErr_SetString(PyExc_TypeError, - "mmap object doesn't support slice deletion"); + "mmap object doesn't support slice deletion"); return -1; } if (! (PyString_Check(v)) ) { @@ -678,6 +718,8 @@ mmap_ass_slice(mmap_object *self, int ilow, int ihigh, PyObject *v) "mmap slice assignment is wrong size"); return -1; } + if (!is_writeable(self)) + return -1; buf = PyString_AsString(v); memcpy(self->data + ilow, buf, ihigh-ilow); return 0; @@ -695,14 +737,16 @@ mmap_ass_item(mmap_object *self, int i, PyObject *v) } if (v == NULL) { PyErr_SetString(PyExc_TypeError, - "mmap object doesn't support item deletion"); + "mmap object doesn't support item deletion"); return -1; } if (! (PyString_Check(v) && PyString_Size(v)==1) ) { PyErr_SetString(PyExc_IndexError, - "mmap assignment must be single-character string"); + "mmap assignment must be single-character string"); return -1; } + if (!is_writeable(self)) + return -1; buf = PyString_AsString(v); self->data[i] = buf[0]; return 0; @@ -792,18 +836,18 @@ _GetMapSize(PyObject *o) } else { PyErr_SetString(PyExc_TypeError, - "map size must be an integral value"); + "map size must be an integral value"); return -1; } -onnegoverflow: + onnegoverflow: PyErr_SetString(PyExc_OverflowError, - "memory mapped size must be positive"); + "memory mapped size must be positive"); return -1; -onposoverflow: + onposoverflow: PyErr_SetString(PyExc_OverflowError, - "memory mapped size is too large (limited by C int)"); + "memory mapped size is too large (limited by C int)"); return -1; } @@ -815,16 +859,42 @@ new_mmap_object(PyObject *self, PyObject *args, PyObject *kwdict) PyObject *map_size_obj = NULL; int map_size; int fd, flags = MAP_SHARED, prot = PROT_WRITE | PROT_READ; - char *keywords[] = {"file", "size", "flags", "prot", NULL}; + access_mode access = ACCESS_DEFAULT; + char *keywords[] = {"fileno", "length", + "flags", "prot", + "access", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwdict, - "iO|ii", keywords, - &fd, &map_size_obj, &flags, &prot) - ) + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iO|iii", keywords, + &fd, &map_size_obj, &flags, &prot, &access)) return NULL; map_size = _GetMapSize(map_size_obj); if (map_size < 0) return NULL; + + if ((access != ACCESS_DEFAULT) && + ((flags != MAP_SHARED) || ( prot != (PROT_WRITE | PROT_READ)))) + return PyErr_Format(PyExc_ValueError, + "mmap can't specify both access and flags, prot."); + switch(access) { + case ACCESS_READ: + flags = MAP_SHARED; + prot = PROT_READ; + break; + case ACCESS_WRITE: + flags = MAP_SHARED; + prot = PROT_READ | PROT_WRITE; + break; + case ACCESS_COPY: + flags = MAP_PRIVATE; + prot = PROT_READ | PROT_WRITE; + break; + case ACCESS_DEFAULT: + /* use the specified or default values of flags and prot */ + break; + default: + return PyErr_Format(PyExc_ValueError, + "mmap invalid access parameter."); + } m_obj = PyObject_New (mmap_object, &mmap_object_type); if (m_obj == NULL) {return NULL;} @@ -834,37 +904,57 @@ new_mmap_object(PyObject *self, PyObject *args, PyObject *kwdict) m_obj->data = mmap(NULL, map_size, prot, flags, fd, 0); - if (m_obj->data == (char *)-1) - { + if (m_obj->data == (char *)-1) { Py_DECREF(m_obj); PyErr_SetFromErrno(mmap_module_error); return NULL; } + m_obj->access = access; return (PyObject *)m_obj; } #endif /* UNIX */ #ifdef MS_WIN32 static PyObject * -new_mmap_object(PyObject *self, PyObject *args) +new_mmap_object(PyObject *self, PyObject *args, PyObject *kwdict) { mmap_object *m_obj; PyObject *map_size_obj = NULL; int map_size; char *tagname = ""; - DWORD dwErr = 0; int fileno; HANDLE fh = 0; - - if (!PyArg_ParseTuple(args, - "iO|z", - &fileno, - &map_size_obj, - &tagname) - ) + access_mode access = ACCESS_DEFAULT; + DWORD flProtect, dwDesiredAccess; + char *keywords[] = { "fileno", "length", + "tagname", + "access", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iO|zi", keywords, + &fileno, &map_size_obj, + &tagname, &access)) { return NULL; - + } + + switch(access) { + case ACCESS_READ: + flProtect = PAGE_READONLY; + dwDesiredAccess = FILE_MAP_READ; + break; + case ACCESS_DEFAULT: case ACCESS_WRITE: + flProtect = PAGE_READWRITE; + dwDesiredAccess = FILE_MAP_WRITE; + break; + case ACCESS_COPY: + flProtect = PAGE_WRITECOPY; + dwDesiredAccess = FILE_MAP_COPY; + break; + default: + return PyErr_Format(PyExc_ValueError, + "mmap invalid access parameter."); + } + map_size = _GetMapSize(map_size_obj); if (map_size < 0) return NULL; @@ -873,8 +963,8 @@ new_mmap_object(PyObject *self, PyObject *args) if (fileno != 0) { fh = (HANDLE)_get_osfhandle(fileno); if (fh==(HANDLE)-1) { - PyErr_SetFromErrno(mmap_module_error); - return NULL; + PyErr_SetFromErrno(mmap_module_error); + return NULL; } /* Win9x appears to need us seeked to zero */ fseek(&_iob[fileno], 0, SEEK_SET); @@ -894,13 +984,13 @@ new_mmap_object(PyObject *self, PyObject *args) /* It is necessary to duplicate the handle, so the Python code can close it on us */ if (!DuplicateHandle( - GetCurrentProcess(), /* source process handle */ - fh, /* handle to be duplicated */ - GetCurrentProcess(), /* target proc handle */ - (LPHANDLE)&m_obj->file_handle, /* result */ - 0, /* access - ignored due to options value */ - FALSE, /* inherited by child processes? */ - DUPLICATE_SAME_ACCESS)) { /* options */ + GetCurrentProcess(), /* source process handle */ + fh, /* handle to be duplicated */ + GetCurrentProcess(), /* target proc handle */ + (LPHANDLE)&m_obj->file_handle, /* result */ + 0, /* access - ignored due to options value */ + FALSE, /* inherited by child processes? */ + DUPLICATE_SAME_ACCESS)) { /* options */ dwErr = GetLastError(); Py_DECREF(m_obj); PyErr_SetFromWindowsErr(dwErr); @@ -932,22 +1022,23 @@ new_mmap_object(PyObject *self, PyObject *args) else m_obj->tagname = NULL; + m_obj->access = access; m_obj->map_handle = CreateFileMapping (m_obj->file_handle, NULL, - PAGE_READWRITE, + flProtect, 0, m_obj->size, m_obj->tagname); if (m_obj->map_handle != NULL) { m_obj->data = (char *) MapViewOfFile (m_obj->map_handle, - FILE_MAP_WRITE, + dwDesiredAccess, 0, 0, 0); if (m_obj->data != NULL) { return ((PyObject *) m_obj); } else { - dwErr = GetLastError(); + dwErr = GetLastError(); } } else { dwErr = GetLastError(); @@ -966,7 +1057,7 @@ static struct PyMethodDef mmap_functions[] = { }; DL_EXPORT(void) -initmmap(void) + initmmap(void) { PyObject *dict, *module; @@ -1011,5 +1102,11 @@ initmmap(void) PyDict_SetItemString (dict, "PAGESIZE", PyInt_FromLong( (long)my_getpagesize() ) ); -} + PyDict_SetItemString (dict, "ACCESS_READ", + PyInt_FromLong(ACCESS_READ)); + PyDict_SetItemString (dict, "ACCESS_WRITE", + PyInt_FromLong(ACCESS_WRITE)); + PyDict_SetItemString (dict, "ACCESS_COPY", + PyInt_FromLong(ACCESS_COPY)); +} -- cgit v0.12