From 5ebfd36afa9093099ab23e4d1256274ae7ee6978 Mon Sep 17 00:00:00 2001
From: Tim Peters <tim.peters@gmail.com>
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

