LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
safeFileIo.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2015 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 """
24 Utilities for safe file IO
25 """
26 from contextlib import contextmanager
27 import errno
28 import filecmp
29 import os
30 import tempfile
31 
32 
33 def safeMakeDir(directory):
34  """Make a directory in a manner avoiding race conditions"""
35  if directory != "" and not os.path.exists(directory):
36  try:
37  os.makedirs(directory)
38  except OSError as e:
39  # Don't fail if directory exists due to race
40  if e.errno != errno.EEXIST:
41  raise e
42 
43 
44 def setFileMode(filename):
45  """Set a file mode according to the user's umask"""
46  # Get the current umask, which we can only do by setting it and then reverting to the original.
47  umask = os.umask(0o077)
48  os.umask(umask)
49  # chmod the new file to match what it would have been if it hadn't started life as a temporary
50  # file (which have more restricted permissions).
51  os.chmod(filename, (~umask & 0o666))
52 
53 
54 @contextmanager
56  """Context manager to get a file that can be written only once and all other writes will succeed only if
57  they match the inital write.
58 
59  The context manager provides a temporary file object. After the user is done, the temporary file becomes
60  the permanent file if the file at name does not already exist. If the file at name does exist the
61  temporary file is compared to the file at name. If they are the same then this is good and the temp file
62  is silently thrown away. If they are not the same then a runtime error is raised.
63  """
64  outDir, outName = os.path.split(name)
65  safeMakeDir(outDir)
66  temp = tempfile.NamedTemporaryFile(mode="w", dir=outDir, prefix=outName, delete=False)
67  try:
68  yield temp
69  finally:
70  try:
71  temp.close()
72  # If the symlink cannot be created then it will raise. If it can't be created because a file at
73  # 'name' already exists then we'll do a compare-same check.
74  os.symlink(temp.name, name)
75  # If the symlink was created then this is the process that created the first instance of the
76  # file, and we know its contents match. Move the temp file over the symlink.
77  os.rename(temp.name, name)
78  except OSError as e:
79  if e.errno != errno.EEXIST:
80  raise e
81  filesMatch = filecmp.cmp(temp.name, name, shallow=False)
82  os.remove(temp.name)
83  if filesMatch:
84  # if the files match then the compare-same check succeeded and we can silently return.
85  return
86  else:
87  # if the files do not match then the calling code was trying to write a non-matching file over
88  # the previous file, maybe it's a race condition? Iny any event, raise a runtime error.
89  raise RuntimeError("Written file does not match existing file.")
90 
91 
92 @contextmanager
93 def SafeFile(name):
94  """Context manager to create a file in a manner avoiding race conditions
95 
96  The context manager provides a temporary file object. After the user is done,
97  we move that file into the desired place and close the fd to avoid resource
98  leakage.
99  """
100  outDir, outName = os.path.split(name)
101  safeMakeDir(outDir)
102  with tempfile.NamedTemporaryFile(mode="w", dir=outDir, prefix=outName, delete=False) as temp:
103  try:
104  yield temp
105  finally:
106  os.rename(temp.name, name)
107  setFileMode(name)
108 
109 
110 @contextmanager
111 def SafeFilename(name):
112  """Context manager for creating a file in a manner avoiding race conditions
113 
114  The context manager provides a temporary filename with no open file descriptors
115  (as this can cause trouble on some systems). After the user is done, we move the
116  file into the desired place.
117  """
118  outDir, outName = os.path.split(name)
119  safeMakeDir(outDir)
120  temp = tempfile.NamedTemporaryFile(mode="w", dir=outDir, prefix=outName, delete=False)
121  tempName = temp.name
122  temp.close() # We don't use the fd, just want a filename
123  try:
124  yield tempName
125  finally:
126  os.rename(tempName, name)
127  setFileMode(name)