Issue #14099: Backout changeset c2c4cde55f6f (except adapted tests).
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 7619cfe..b77e6c8 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -498,25 +498,6 @@
 }
 
 
-class _SharedFile:
-    def __init__(self, file, pos, close):
-        self._file = file
-        self._pos = pos
-        self._close = close
-
-    def read(self, n=-1):
-        self._file.seek(self._pos)
-        data = self._file.read(n)
-        self._pos = self._file.tell()
-        return data
-
-    def close(self):
-        if self._file is not None:
-            fileobj = self._file
-            self._file = None
-            self._close(fileobj)
-
-
 class ZipExtFile(io.BufferedIOBase):
     """File-like object for reading an archive member.
        Is returned by ZipFile.open().
@@ -762,7 +743,7 @@
         self.NameToInfo = {}    # Find file info given name
         self.filelist = []      # List of ZipInfo instances for archive
         self.compression = compression  # Method of compression
-        self.mode = mode
+        self.mode = key = mode.replace('b', '')[0]
         self.pwd = None
         self._comment = ''
 
@@ -770,33 +751,28 @@
         if isinstance(file, basestring):
             self._filePassed = 0
             self.filename = file
-            modeDict = {'r' : 'rb', 'w': 'w+b', 'a' : 'r+b',
-                        'r+b': 'w+b', 'w+b': 'wb'}
-            filemode = modeDict[mode]
-            while True:
-                try:
-                    self.fp = io.open(file, filemode)
-                except IOError:
-                    if filemode in modeDict:
-                        filemode = modeDict[filemode]
-                        continue
+            modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
+            try:
+                self.fp = open(file, modeDict[mode])
+            except IOError:
+                if mode == 'a':
+                    mode = key = 'w'
+                    self.fp = open(file, modeDict[mode])
+                else:
                     raise
-                break
         else:
             self._filePassed = 1
             self.fp = file
             self.filename = getattr(file, 'name', None)
-        self._fileRefCnt = 1
 
         try:
-            if mode == 'r':
+            if key == 'r':
                 self._RealGetContents()
-            elif mode == 'w':
+            elif key == 'w':
                 # set the modified flag so central directory gets written
                 # even if no files are added to the archive
                 self._didModify = True
-                self.start_dir = 0
-            elif mode == 'a':
+            elif key == 'a':
                 try:
                     # See if file is a zip file
                     self._RealGetContents()
@@ -809,13 +785,13 @@
                     # set the modified flag so central directory gets written
                     # even if no files are added to the archive
                     self._didModify = True
-                    self.start_dir = self.fp.tell()
             else:
                 raise RuntimeError('Mode must be "r", "w" or "a"')
         except:
             fp = self.fp
             self.fp = None
-            self._fpclose(fp)
+            if not self._filePassed:
+                fp.close()
             raise
 
     def __enter__(self):
@@ -966,17 +942,26 @@
             raise RuntimeError, \
                   "Attempt to read ZIP archive that was already closed"
 
-        # Make sure we have an info object
-        if isinstance(name, ZipInfo):
-            # 'name' is already an info object
-            zinfo = name
+        # Only open a new file for instances where we were not
+        # given a file object in the constructor
+        if self._filePassed:
+            zef_file = self.fp
+            should_close = False
         else:
-            # Get info object for name
-            zinfo = self.getinfo(name)
+            zef_file = open(self.filename, 'rb')
+            should_close = True
 
-        self._fileRefCnt += 1
-        zef_file = _SharedFile(self.fp, zinfo.header_offset, self._fpclose)
         try:
+            # Make sure we have an info object
+            if isinstance(name, ZipInfo):
+                # 'name' is already an info object
+                zinfo = name
+            else:
+                # Get info object for name
+                zinfo = self.getinfo(name)
+
+            zef_file.seek(zinfo.header_offset, 0)
+
             # Skip the file header:
             fheader = zef_file.read(sizeFileHeader)
             if len(fheader) != sizeFileHeader:
@@ -1021,9 +1006,11 @@
                 if ord(h[11]) != check_byte:
                     raise RuntimeError("Bad password for file", name)
 
-            return ZipExtFile(zef_file, mode, zinfo, zd, True)
+            return ZipExtFile(zef_file, mode, zinfo, zd,
+                    close_fileobj=should_close)
         except:
-            zef_file.close()
+            if should_close:
+                zef_file.close()
             raise
 
     def extract(self, member, path=None, pwd=None):
@@ -1154,7 +1141,6 @@
 
         zinfo.file_size = st.st_size
         zinfo.flag_bits = 0x00
-        self.fp.seek(self.start_dir, 0)
         zinfo.header_offset = self.fp.tell()    # Start of header bytes
 
         self._writecheck(zinfo)
@@ -1168,7 +1154,6 @@
             self.filelist.append(zinfo)
             self.NameToInfo[zinfo.filename] = zinfo
             self.fp.write(zinfo.FileHeader(False))
-            self.start_dir = self.fp.tell()
             return
 
         with open(filename, "rb") as fp:
@@ -1211,10 +1196,10 @@
                 raise RuntimeError('Compressed size larger than uncompressed size')
         # Seek backwards and write file header (which will now include
         # correct CRC and file sizes)
-        self.start_dir = self.fp.tell()       # Preserve current position in file
+        position = self.fp.tell()       # Preserve current position in file
         self.fp.seek(zinfo.header_offset, 0)
         self.fp.write(zinfo.FileHeader(zip64))
-        self.fp.seek(self.start_dir, 0)
+        self.fp.seek(position, 0)
         self.filelist.append(zinfo)
         self.NameToInfo[zinfo.filename] = zinfo
 
@@ -1243,7 +1228,6 @@
             zinfo.compress_type = compress_type
 
         zinfo.file_size = len(bytes)            # Uncompressed size
-        self.fp.seek(self.start_dir, 0)
         zinfo.header_offset = self.fp.tell()    # Start of header bytes
         self._writecheck(zinfo)
         self._didModify = True
@@ -1267,7 +1251,6 @@
             self.fp.write(struct.pack(fmt, zinfo.CRC, zinfo.compress_size,
                   zinfo.file_size))
         self.fp.flush()
-        self.start_dir = self.fp.tell()
         self.filelist.append(zinfo)
         self.NameToInfo[zinfo.filename] = zinfo
 
@@ -1283,7 +1266,7 @@
 
         try:
             if self.mode in ("w", "a") and self._didModify: # write ending records
-                self.fp.seek(self.start_dir, 0)
+                pos1 = self.fp.tell()
                 for zinfo in self.filelist:         # write central directory
                     dt = zinfo.date_time
                     dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
@@ -1346,8 +1329,8 @@
                 pos2 = self.fp.tell()
                 # Write end-of-zip-archive record
                 centDirCount = len(self.filelist)
-                centDirSize = pos2 - self.start_dir
-                centDirOffset = self.start_dir
+                centDirSize = pos2 - pos1
+                centDirOffset = pos1
                 requires_zip64 = None
                 if centDirCount > ZIP_FILECOUNT_LIMIT:
                     requires_zip64 = "Files count"
@@ -1383,13 +1366,8 @@
         finally:
             fp = self.fp
             self.fp = None
-            self._fpclose(fp)
-
-    def _fpclose(self, fp):
-        assert self._fileRefCnt > 0
-        self._fileRefCnt -= 1
-        if not self._fileRefCnt and not self._filePassed:
-            fp.close()
+            if not self._filePassed:
+                fp.close()
 
 
 class PyZipFile(ZipFile):