Statistics
| Branch: | Tag: | Revision:

root / lib / utils / io.py @ adec726e

History | View | Annotate | Download (30.2 kB)

1 3865ca48 Michael Hanselmann
#
2 3865ca48 Michael Hanselmann
#
3 3865ca48 Michael Hanselmann
4 adec726e Jose A. Lopes
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2014 Google Inc.
5 3865ca48 Michael Hanselmann
#
6 3865ca48 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 3865ca48 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 3865ca48 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 3865ca48 Michael Hanselmann
# (at your option) any later version.
10 3865ca48 Michael Hanselmann
#
11 3865ca48 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 3865ca48 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 3865ca48 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 3865ca48 Michael Hanselmann
# General Public License for more details.
15 3865ca48 Michael Hanselmann
#
16 3865ca48 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 3865ca48 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 3865ca48 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 3865ca48 Michael Hanselmann
# 02110-1301, USA.
20 3865ca48 Michael Hanselmann
21 3865ca48 Michael Hanselmann
"""Utility functions for I/O.
22 3865ca48 Michael Hanselmann

23 3865ca48 Michael Hanselmann
"""
24 3865ca48 Michael Hanselmann
25 3865ca48 Michael Hanselmann
import os
26 f15a6b03 Jose A. Lopes
import re
27 3865ca48 Michael Hanselmann
import logging
28 3865ca48 Michael Hanselmann
import shutil
29 3865ca48 Michael Hanselmann
import tempfile
30 3865ca48 Michael Hanselmann
import errno
31 3865ca48 Michael Hanselmann
import time
32 b81b3c96 René Nussbaumer
import stat
33 fc84ef94 Michele Tartara
import grp
34 fc84ef94 Michele Tartara
import pwd
35 3865ca48 Michael Hanselmann
36 3865ca48 Michael Hanselmann
from ganeti import errors
37 3865ca48 Michael Hanselmann
from ganeti import constants
38 111a7d04 Michael Hanselmann
from ganeti import pathutils
39 3865ca48 Michael Hanselmann
from ganeti.utils import filelock
40 3865ca48 Michael Hanselmann
41 2dbc6857 Michael Hanselmann
#: Directory used by fsck(8) to store recovered data, usually at a file
42 2dbc6857 Michael Hanselmann
#: system's root directory
43 2dbc6857 Michael Hanselmann
_LOST_AND_FOUND = "lost+found"
44 2dbc6857 Michael Hanselmann
45 c47eddb8 Bernardo Dal Seno
# Possible values for keep_perms in WriteFile()
46 c47eddb8 Bernardo Dal Seno
KP_NEVER = 0
47 c47eddb8 Bernardo Dal Seno
KP_ALWAYS = 1
48 c47eddb8 Bernardo Dal Seno
KP_IF_EXISTS = 2
49 c47eddb8 Bernardo Dal Seno
50 c47eddb8 Bernardo Dal Seno
KEEP_PERMS_VALUES = [
51 c47eddb8 Bernardo Dal Seno
  KP_NEVER,
52 c47eddb8 Bernardo Dal Seno
  KP_ALWAYS,
53 c47eddb8 Bernardo Dal Seno
  KP_IF_EXISTS,
54 c47eddb8 Bernardo Dal Seno
  ]
55 c47eddb8 Bernardo Dal Seno
56 90e234a6 Michael Hanselmann
57 eb93b673 Guido Trotter
def ErrnoOrStr(err):
58 eb93b673 Guido Trotter
  """Format an EnvironmentError exception.
59 eb93b673 Guido Trotter

60 eb93b673 Guido Trotter
  If the L{err} argument has an errno attribute, it will be looked up
61 eb93b673 Guido Trotter
  and converted into a textual C{E...} description. Otherwise the
62 eb93b673 Guido Trotter
  string representation of the error will be returned.
63 eb93b673 Guido Trotter

64 eb93b673 Guido Trotter
  @type err: L{EnvironmentError}
65 eb93b673 Guido Trotter
  @param err: the exception to format
66 eb93b673 Guido Trotter

67 eb93b673 Guido Trotter
  """
68 eb93b673 Guido Trotter
  if hasattr(err, "errno"):
69 eb93b673 Guido Trotter
    detail = errno.errorcode[err.errno]
70 eb93b673 Guido Trotter
  else:
71 eb93b673 Guido Trotter
    detail = str(err)
72 eb93b673 Guido Trotter
  return detail
73 eb93b673 Guido Trotter
74 eb93b673 Guido Trotter
75 2635bb04 Michael Hanselmann
class FileStatHelper:
76 2635bb04 Michael Hanselmann
  """Helper to store file handle's C{fstat}.
77 2635bb04 Michael Hanselmann

78 2635bb04 Michael Hanselmann
  Useful in combination with L{ReadFile}'s C{preread} parameter.
79 2635bb04 Michael Hanselmann

80 2635bb04 Michael Hanselmann
  """
81 2635bb04 Michael Hanselmann
  def __init__(self):
82 2635bb04 Michael Hanselmann
    """Initializes this class.
83 2635bb04 Michael Hanselmann

84 2635bb04 Michael Hanselmann
    """
85 2635bb04 Michael Hanselmann
    self.st = None
86 2635bb04 Michael Hanselmann
87 2635bb04 Michael Hanselmann
  def __call__(self, fh):
88 2635bb04 Michael Hanselmann
    """Calls C{fstat} on file handle.
89 2635bb04 Michael Hanselmann

90 2635bb04 Michael Hanselmann
    """
91 2635bb04 Michael Hanselmann
    self.st = os.fstat(fh.fileno())
92 2635bb04 Michael Hanselmann
93 2635bb04 Michael Hanselmann
94 0e5084ee Michael Hanselmann
def ReadFile(file_name, size=-1, preread=None):
95 3865ca48 Michael Hanselmann
  """Reads a file.
96 3865ca48 Michael Hanselmann

97 3865ca48 Michael Hanselmann
  @type size: int
98 3865ca48 Michael Hanselmann
  @param size: Read at most size bytes (if negative, entire file)
99 0e5084ee Michael Hanselmann
  @type preread: callable receiving file handle as single parameter
100 0e5084ee Michael Hanselmann
  @param preread: Function called before file is read
101 3865ca48 Michael Hanselmann
  @rtype: str
102 3865ca48 Michael Hanselmann
  @return: the (possibly partial) content of the file
103 adec726e Jose A. Lopes
  @raise IOError: if the file cannot be opened
104 3865ca48 Michael Hanselmann

105 3865ca48 Michael Hanselmann
  """
106 3865ca48 Michael Hanselmann
  f = open(file_name, "r")
107 3865ca48 Michael Hanselmann
  try:
108 0e5084ee Michael Hanselmann
    if preread:
109 0e5084ee Michael Hanselmann
      preread(f)
110 0e5084ee Michael Hanselmann
111 3865ca48 Michael Hanselmann
    return f.read(size)
112 3865ca48 Michael Hanselmann
  finally:
113 3865ca48 Michael Hanselmann
    f.close()
114 3865ca48 Michael Hanselmann
115 3865ca48 Michael Hanselmann
116 3865ca48 Michael Hanselmann
def WriteFile(file_name, fn=None, data=None,
117 3865ca48 Michael Hanselmann
              mode=None, uid=-1, gid=-1,
118 3865ca48 Michael Hanselmann
              atime=None, mtime=None, close=True,
119 3865ca48 Michael Hanselmann
              dry_run=False, backup=False,
120 c47eddb8 Bernardo Dal Seno
              prewrite=None, postwrite=None, keep_perms=KP_NEVER):
121 3865ca48 Michael Hanselmann
  """(Over)write a file atomically.
122 3865ca48 Michael Hanselmann

123 3865ca48 Michael Hanselmann
  The file_name and either fn (a function taking one argument, the
124 3865ca48 Michael Hanselmann
  file descriptor, and which should write the data to it) or data (the
125 3865ca48 Michael Hanselmann
  contents of the file) must be passed. The other arguments are
126 3865ca48 Michael Hanselmann
  optional and allow setting the file mode, owner and group, and the
127 3865ca48 Michael Hanselmann
  mtime/atime of the file.
128 3865ca48 Michael Hanselmann

129 3865ca48 Michael Hanselmann
  If the function doesn't raise an exception, it has succeeded and the
130 3865ca48 Michael Hanselmann
  target file has the new contents. If the function has raised an
131 3865ca48 Michael Hanselmann
  exception, an existing target file should be unmodified and the
132 3865ca48 Michael Hanselmann
  temporary file should be removed.
133 3865ca48 Michael Hanselmann

134 3865ca48 Michael Hanselmann
  @type file_name: str
135 3865ca48 Michael Hanselmann
  @param file_name: the target filename
136 3865ca48 Michael Hanselmann
  @type fn: callable
137 3865ca48 Michael Hanselmann
  @param fn: content writing function, called with
138 3865ca48 Michael Hanselmann
      file descriptor as parameter
139 3865ca48 Michael Hanselmann
  @type data: str
140 3865ca48 Michael Hanselmann
  @param data: contents of the file
141 3865ca48 Michael Hanselmann
  @type mode: int
142 3865ca48 Michael Hanselmann
  @param mode: file mode
143 3865ca48 Michael Hanselmann
  @type uid: int
144 3865ca48 Michael Hanselmann
  @param uid: the owner of the file
145 3865ca48 Michael Hanselmann
  @type gid: int
146 3865ca48 Michael Hanselmann
  @param gid: the group of the file
147 3865ca48 Michael Hanselmann
  @type atime: int
148 3865ca48 Michael Hanselmann
  @param atime: a custom access time to be set on the file
149 3865ca48 Michael Hanselmann
  @type mtime: int
150 3865ca48 Michael Hanselmann
  @param mtime: a custom modification time to be set on the file
151 3865ca48 Michael Hanselmann
  @type close: boolean
152 3865ca48 Michael Hanselmann
  @param close: whether to close file after writing it
153 3865ca48 Michael Hanselmann
  @type prewrite: callable
154 3865ca48 Michael Hanselmann
  @param prewrite: function to be called before writing content
155 3865ca48 Michael Hanselmann
  @type postwrite: callable
156 3865ca48 Michael Hanselmann
  @param postwrite: function to be called after writing content
157 c47eddb8 Bernardo Dal Seno
  @type keep_perms: members of L{KEEP_PERMS_VALUES}
158 c47eddb8 Bernardo Dal Seno
  @param keep_perms: if L{KP_NEVER} (default), owner, group, and mode are
159 c47eddb8 Bernardo Dal Seno
      taken from the other parameters; if L{KP_ALWAYS}, owner, group, and
160 c47eddb8 Bernardo Dal Seno
      mode are copied from the existing file; if L{KP_IF_EXISTS}, owner,
161 c47eddb8 Bernardo Dal Seno
      group, and mode are taken from the file, and if the file doesn't
162 c47eddb8 Bernardo Dal Seno
      exist, they are taken from the other parameters. It is an error to
163 c47eddb8 Bernardo Dal Seno
      pass L{KP_ALWAYS} when the file doesn't exist or when C{uid}, C{gid},
164 c47eddb8 Bernardo Dal Seno
      or C{mode} are set to non-default values.
165 3865ca48 Michael Hanselmann

166 3865ca48 Michael Hanselmann
  @rtype: None or int
167 3865ca48 Michael Hanselmann
  @return: None if the 'close' parameter evaluates to True,
168 3865ca48 Michael Hanselmann
      otherwise the file descriptor
169 3865ca48 Michael Hanselmann

170 3865ca48 Michael Hanselmann
  @raise errors.ProgrammerError: if any of the arguments are not valid
171 3865ca48 Michael Hanselmann

172 3865ca48 Michael Hanselmann
  """
173 3865ca48 Michael Hanselmann
  if not os.path.isabs(file_name):
174 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Path passed to WriteFile is not"
175 3865ca48 Michael Hanselmann
                                 " absolute: '%s'" % file_name)
176 3865ca48 Michael Hanselmann
177 3865ca48 Michael Hanselmann
  if [fn, data].count(None) != 1:
178 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("fn or data required")
179 3865ca48 Michael Hanselmann
180 3865ca48 Michael Hanselmann
  if [atime, mtime].count(None) == 1:
181 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Both atime and mtime must be either"
182 3865ca48 Michael Hanselmann
                                 " set or None")
183 3865ca48 Michael Hanselmann
184 c47eddb8 Bernardo Dal Seno
  if not keep_perms in KEEP_PERMS_VALUES:
185 c47eddb8 Bernardo Dal Seno
    raise errors.ProgrammerError("Invalid value for keep_perms: %s" %
186 c47eddb8 Bernardo Dal Seno
                                 keep_perms)
187 c47eddb8 Bernardo Dal Seno
  if keep_perms == KP_ALWAYS and (uid != -1 or gid != -1 or mode is not None):
188 c47eddb8 Bernardo Dal Seno
    raise errors.ProgrammerError("When keep_perms==KP_ALWAYS, 'uid', 'gid',"
189 c47eddb8 Bernardo Dal Seno
                                 " and 'mode' cannot be set")
190 c47eddb8 Bernardo Dal Seno
191 3865ca48 Michael Hanselmann
  if backup and not dry_run and os.path.isfile(file_name):
192 3865ca48 Michael Hanselmann
    CreateBackup(file_name)
193 3865ca48 Michael Hanselmann
194 c47eddb8 Bernardo Dal Seno
  if keep_perms == KP_ALWAYS or keep_perms == KP_IF_EXISTS:
195 c47eddb8 Bernardo Dal Seno
    # os.stat() raises an exception if the file doesn't exist
196 c47eddb8 Bernardo Dal Seno
    try:
197 c47eddb8 Bernardo Dal Seno
      file_stat = os.stat(file_name)
198 c47eddb8 Bernardo Dal Seno
      mode = stat.S_IMODE(file_stat.st_mode)
199 c47eddb8 Bernardo Dal Seno
      uid = file_stat.st_uid
200 c47eddb8 Bernardo Dal Seno
      gid = file_stat.st_gid
201 c47eddb8 Bernardo Dal Seno
    except OSError:
202 c47eddb8 Bernardo Dal Seno
      if keep_perms == KP_ALWAYS:
203 c47eddb8 Bernardo Dal Seno
        raise
204 c47eddb8 Bernardo Dal Seno
      # else: if keeep_perms == KP_IF_EXISTS it's ok if the file doesn't exist
205 c47eddb8 Bernardo Dal Seno
206 a9d68e40 Michael Hanselmann
  # Whether temporary file needs to be removed (e.g. if any error occurs)
207 3865ca48 Michael Hanselmann
  do_remove = True
208 a9d68e40 Michael Hanselmann
209 a9d68e40 Michael Hanselmann
  # Function result
210 a9d68e40 Michael Hanselmann
  result = None
211 a9d68e40 Michael Hanselmann
212 a9d68e40 Michael Hanselmann
  (dir_name, base_name) = os.path.split(file_name)
213 a9d68e40 Michael Hanselmann
  (fd, new_name) = tempfile.mkstemp(suffix=".new", prefix=base_name,
214 a9d68e40 Michael Hanselmann
                                    dir=dir_name)
215 3865ca48 Michael Hanselmann
  try:
216 a9d68e40 Michael Hanselmann
    try:
217 a9d68e40 Michael Hanselmann
      if uid != -1 or gid != -1:
218 a9d68e40 Michael Hanselmann
        os.chown(new_name, uid, gid)
219 a9d68e40 Michael Hanselmann
      if mode:
220 a9d68e40 Michael Hanselmann
        os.chmod(new_name, mode)
221 a9d68e40 Michael Hanselmann
      if callable(prewrite):
222 a9d68e40 Michael Hanselmann
        prewrite(fd)
223 a9d68e40 Michael Hanselmann
      if data is not None:
224 1d39e245 Iustin Pop
        if isinstance(data, unicode):
225 1d39e245 Iustin Pop
          data = data.encode()
226 1d39e245 Iustin Pop
        assert isinstance(data, str)
227 437c3e77 Iustin Pop
        to_write = len(data)
228 437c3e77 Iustin Pop
        offset = 0
229 437c3e77 Iustin Pop
        while offset < to_write:
230 437c3e77 Iustin Pop
          written = os.write(fd, buffer(data, offset))
231 437c3e77 Iustin Pop
          assert written >= 0
232 1d39e245 Iustin Pop
          assert written <= to_write - offset
233 437c3e77 Iustin Pop
          offset += written
234 437c3e77 Iustin Pop
        assert offset == to_write
235 a9d68e40 Michael Hanselmann
      else:
236 a9d68e40 Michael Hanselmann
        fn(fd)
237 a9d68e40 Michael Hanselmann
      if callable(postwrite):
238 a9d68e40 Michael Hanselmann
        postwrite(fd)
239 a9d68e40 Michael Hanselmann
      os.fsync(fd)
240 a9d68e40 Michael Hanselmann
      if atime is not None and mtime is not None:
241 a9d68e40 Michael Hanselmann
        os.utime(new_name, (atime, mtime))
242 a9d68e40 Michael Hanselmann
    finally:
243 a9d68e40 Michael Hanselmann
      # Close file unless the file descriptor should be returned
244 a9d68e40 Michael Hanselmann
      if close:
245 a9d68e40 Michael Hanselmann
        os.close(fd)
246 a9d68e40 Michael Hanselmann
      else:
247 a9d68e40 Michael Hanselmann
        result = fd
248 a9d68e40 Michael Hanselmann
249 a9d68e40 Michael Hanselmann
    # Rename file to destination name
250 3865ca48 Michael Hanselmann
    if not dry_run:
251 3865ca48 Michael Hanselmann
      os.rename(new_name, file_name)
252 a9d68e40 Michael Hanselmann
      # Successful, no need to remove anymore
253 3865ca48 Michael Hanselmann
      do_remove = False
254 3865ca48 Michael Hanselmann
  finally:
255 3865ca48 Michael Hanselmann
    if do_remove:
256 3865ca48 Michael Hanselmann
      RemoveFile(new_name)
257 3865ca48 Michael Hanselmann
258 3865ca48 Michael Hanselmann
  return result
259 3865ca48 Michael Hanselmann
260 3865ca48 Michael Hanselmann
261 3865ca48 Michael Hanselmann
def GetFileID(path=None, fd=None):
262 3865ca48 Michael Hanselmann
  """Returns the file 'id', i.e. the dev/inode and mtime information.
263 3865ca48 Michael Hanselmann

264 3865ca48 Michael Hanselmann
  Either the path to the file or the fd must be given.
265 3865ca48 Michael Hanselmann

266 3865ca48 Michael Hanselmann
  @param path: the file path
267 3865ca48 Michael Hanselmann
  @param fd: a file descriptor
268 3865ca48 Michael Hanselmann
  @return: a tuple of (device number, inode number, mtime)
269 3865ca48 Michael Hanselmann

270 3865ca48 Michael Hanselmann
  """
271 3865ca48 Michael Hanselmann
  if [path, fd].count(None) != 1:
272 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("One and only one of fd/path must be given")
273 3865ca48 Michael Hanselmann
274 3865ca48 Michael Hanselmann
  if fd is None:
275 3865ca48 Michael Hanselmann
    st = os.stat(path)
276 3865ca48 Michael Hanselmann
  else:
277 3865ca48 Michael Hanselmann
    st = os.fstat(fd)
278 3865ca48 Michael Hanselmann
279 3865ca48 Michael Hanselmann
  return (st.st_dev, st.st_ino, st.st_mtime)
280 3865ca48 Michael Hanselmann
281 3865ca48 Michael Hanselmann
282 3865ca48 Michael Hanselmann
def VerifyFileID(fi_disk, fi_ours):
283 3865ca48 Michael Hanselmann
  """Verifies that two file IDs are matching.
284 3865ca48 Michael Hanselmann

285 3865ca48 Michael Hanselmann
  Differences in the inode/device are not accepted, but and older
286 3865ca48 Michael Hanselmann
  timestamp for fi_disk is accepted.
287 3865ca48 Michael Hanselmann

288 3865ca48 Michael Hanselmann
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
289 3865ca48 Michael Hanselmann
      file data
290 3865ca48 Michael Hanselmann
  @param fi_ours: tuple (dev, inode, mtime) representing the last
291 3865ca48 Michael Hanselmann
      written file data
292 3865ca48 Michael Hanselmann
  @rtype: boolean
293 3865ca48 Michael Hanselmann

294 3865ca48 Michael Hanselmann
  """
295 3865ca48 Michael Hanselmann
  (d1, i1, m1) = fi_disk
296 3865ca48 Michael Hanselmann
  (d2, i2, m2) = fi_ours
297 3865ca48 Michael Hanselmann
298 3865ca48 Michael Hanselmann
  return (d1, i1) == (d2, i2) and m1 <= m2
299 3865ca48 Michael Hanselmann
300 3865ca48 Michael Hanselmann
301 3865ca48 Michael Hanselmann
def SafeWriteFile(file_name, file_id, **kwargs):
302 3865ca48 Michael Hanselmann
  """Wraper over L{WriteFile} that locks the target file.
303 3865ca48 Michael Hanselmann

304 3865ca48 Michael Hanselmann
  By keeping the target file locked during WriteFile, we ensure that
305 3865ca48 Michael Hanselmann
  cooperating writers will safely serialise access to the file.
306 3865ca48 Michael Hanselmann

307 3865ca48 Michael Hanselmann
  @type file_name: str
308 3865ca48 Michael Hanselmann
  @param file_name: the target filename
309 3865ca48 Michael Hanselmann
  @type file_id: tuple
310 3865ca48 Michael Hanselmann
  @param file_id: a result from L{GetFileID}
311 3865ca48 Michael Hanselmann

312 3865ca48 Michael Hanselmann
  """
313 3865ca48 Michael Hanselmann
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
314 3865ca48 Michael Hanselmann
  try:
315 3865ca48 Michael Hanselmann
    filelock.LockFile(fd)
316 3865ca48 Michael Hanselmann
    if file_id is not None:
317 3865ca48 Michael Hanselmann
      disk_id = GetFileID(fd=fd)
318 3865ca48 Michael Hanselmann
      if not VerifyFileID(disk_id, file_id):
319 3865ca48 Michael Hanselmann
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
320 3865ca48 Michael Hanselmann
                               " since last written" % file_name)
321 3865ca48 Michael Hanselmann
    return WriteFile(file_name, **kwargs)
322 3865ca48 Michael Hanselmann
  finally:
323 3865ca48 Michael Hanselmann
    os.close(fd)
324 3865ca48 Michael Hanselmann
325 3865ca48 Michael Hanselmann
326 3865ca48 Michael Hanselmann
def ReadOneLineFile(file_name, strict=False):
327 3865ca48 Michael Hanselmann
  """Return the first non-empty line from a file.
328 3865ca48 Michael Hanselmann

329 3865ca48 Michael Hanselmann
  @type strict: boolean
330 3865ca48 Michael Hanselmann
  @param strict: if True, abort if the file has more than one
331 3865ca48 Michael Hanselmann
      non-empty line
332 3865ca48 Michael Hanselmann

333 3865ca48 Michael Hanselmann
  """
334 3865ca48 Michael Hanselmann
  file_lines = ReadFile(file_name).splitlines()
335 3865ca48 Michael Hanselmann
  full_lines = filter(bool, file_lines)
336 3865ca48 Michael Hanselmann
  if not file_lines or not full_lines:
337 3865ca48 Michael Hanselmann
    raise errors.GenericError("No data in one-liner file %s" % file_name)
338 3865ca48 Michael Hanselmann
  elif strict and len(full_lines) > 1:
339 3865ca48 Michael Hanselmann
    raise errors.GenericError("Too many lines in one-liner file %s" %
340 3865ca48 Michael Hanselmann
                              file_name)
341 3865ca48 Michael Hanselmann
  return full_lines[0]
342 3865ca48 Michael Hanselmann
343 3865ca48 Michael Hanselmann
344 3865ca48 Michael Hanselmann
def RemoveFile(filename):
345 3865ca48 Michael Hanselmann
  """Remove a file ignoring some errors.
346 3865ca48 Michael Hanselmann

347 3865ca48 Michael Hanselmann
  Remove a file, ignoring non-existing ones or directories. Other
348 3865ca48 Michael Hanselmann
  errors are passed.
349 3865ca48 Michael Hanselmann

350 3865ca48 Michael Hanselmann
  @type filename: str
351 3865ca48 Michael Hanselmann
  @param filename: the file to be removed
352 3865ca48 Michael Hanselmann

353 3865ca48 Michael Hanselmann
  """
354 3865ca48 Michael Hanselmann
  try:
355 3865ca48 Michael Hanselmann
    os.unlink(filename)
356 3865ca48 Michael Hanselmann
  except OSError, err:
357 3865ca48 Michael Hanselmann
    if err.errno not in (errno.ENOENT, errno.EISDIR):
358 3865ca48 Michael Hanselmann
      raise
359 3865ca48 Michael Hanselmann
360 3865ca48 Michael Hanselmann
361 3865ca48 Michael Hanselmann
def RemoveDir(dirname):
362 3865ca48 Michael Hanselmann
  """Remove an empty directory.
363 3865ca48 Michael Hanselmann

364 3865ca48 Michael Hanselmann
  Remove a directory, ignoring non-existing ones.
365 3865ca48 Michael Hanselmann
  Other errors are passed. This includes the case,
366 3865ca48 Michael Hanselmann
  where the directory is not empty, so it can't be removed.
367 3865ca48 Michael Hanselmann

368 3865ca48 Michael Hanselmann
  @type dirname: str
369 3865ca48 Michael Hanselmann
  @param dirname: the empty directory to be removed
370 3865ca48 Michael Hanselmann

371 3865ca48 Michael Hanselmann
  """
372 3865ca48 Michael Hanselmann
  try:
373 3865ca48 Michael Hanselmann
    os.rmdir(dirname)
374 3865ca48 Michael Hanselmann
  except OSError, err:
375 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
376 3865ca48 Michael Hanselmann
      raise
377 3865ca48 Michael Hanselmann
378 3865ca48 Michael Hanselmann
379 8e5a705d René Nussbaumer
def RenameFile(old, new, mkdir=False, mkdir_mode=0750, dir_uid=None,
380 8e5a705d René Nussbaumer
               dir_gid=None):
381 3865ca48 Michael Hanselmann
  """Renames a file.
382 3865ca48 Michael Hanselmann

383 9c2b3a70 René Nussbaumer
  This just creates the very least directory if it does not exist and C{mkdir}
384 9c2b3a70 René Nussbaumer
  is set to true.
385 9c2b3a70 René Nussbaumer

386 3865ca48 Michael Hanselmann
  @type old: string
387 3865ca48 Michael Hanselmann
  @param old: Original path
388 3865ca48 Michael Hanselmann
  @type new: string
389 3865ca48 Michael Hanselmann
  @param new: New path
390 3865ca48 Michael Hanselmann
  @type mkdir: bool
391 3865ca48 Michael Hanselmann
  @param mkdir: Whether to create target directory if it doesn't exist
392 3865ca48 Michael Hanselmann
  @type mkdir_mode: int
393 3865ca48 Michael Hanselmann
  @param mkdir_mode: Mode for newly created directories
394 8e5a705d René Nussbaumer
  @type dir_uid: int
395 8e5a705d René Nussbaumer
  @param dir_uid: The uid for the (if fresh created) dir
396 8e5a705d René Nussbaumer
  @type dir_gid: int
397 8e5a705d René Nussbaumer
  @param dir_gid: The gid for the (if fresh created) dir
398 3865ca48 Michael Hanselmann

399 3865ca48 Michael Hanselmann
  """
400 3865ca48 Michael Hanselmann
  try:
401 3865ca48 Michael Hanselmann
    return os.rename(old, new)
402 3865ca48 Michael Hanselmann
  except OSError, err:
403 3865ca48 Michael Hanselmann
    # In at least one use case of this function, the job queue, directory
404 3865ca48 Michael Hanselmann
    # creation is very rare. Checking for the directory before renaming is not
405 3865ca48 Michael Hanselmann
    # as efficient.
406 3865ca48 Michael Hanselmann
    if mkdir and err.errno == errno.ENOENT:
407 3865ca48 Michael Hanselmann
      # Create directory and try again
408 8e5a705d René Nussbaumer
      dir_path = os.path.dirname(new)
409 9c2b3a70 René Nussbaumer
      MakeDirWithPerm(dir_path, mkdir_mode, dir_uid, dir_gid)
410 3865ca48 Michael Hanselmann
411 3865ca48 Michael Hanselmann
      return os.rename(old, new)
412 3865ca48 Michael Hanselmann
413 3865ca48 Michael Hanselmann
    raise
414 3865ca48 Michael Hanselmann
415 3865ca48 Michael Hanselmann
416 9c2b3a70 René Nussbaumer
def EnforcePermission(path, mode, uid=None, gid=None, must_exist=True,
417 b81b3c96 René Nussbaumer
                      _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
418 b81b3c96 René Nussbaumer
  """Enforces that given path has given permissions.
419 b81b3c96 René Nussbaumer

420 b81b3c96 René Nussbaumer
  @param path: The path to the file
421 b81b3c96 René Nussbaumer
  @param mode: The mode of the file
422 b81b3c96 René Nussbaumer
  @param uid: The uid of the owner of this file
423 b81b3c96 René Nussbaumer
  @param gid: The gid of the owner of this file
424 b81b3c96 René Nussbaumer
  @param must_exist: Specifies if non-existance of path will be an error
425 b81b3c96 René Nussbaumer
  @param _chmod_fn: chmod function to use (unittest only)
426 b81b3c96 René Nussbaumer
  @param _chown_fn: chown function to use (unittest only)
427 b81b3c96 René Nussbaumer

428 b81b3c96 René Nussbaumer
  """
429 b81b3c96 René Nussbaumer
  logging.debug("Checking %s", path)
430 9c2b3a70 René Nussbaumer
431 9c2b3a70 René Nussbaumer
  # chown takes -1 if you want to keep one part of the ownership, however
432 9c2b3a70 René Nussbaumer
  # None is Python standard for that. So we remap them here.
433 9c2b3a70 René Nussbaumer
  if uid is None:
434 9c2b3a70 René Nussbaumer
    uid = -1
435 9c2b3a70 René Nussbaumer
  if gid is None:
436 9c2b3a70 René Nussbaumer
    gid = -1
437 9c2b3a70 René Nussbaumer
438 b81b3c96 René Nussbaumer
  try:
439 b81b3c96 René Nussbaumer
    st = _stat_fn(path)
440 b81b3c96 René Nussbaumer
441 b81b3c96 René Nussbaumer
    fmode = stat.S_IMODE(st[stat.ST_MODE])
442 b81b3c96 René Nussbaumer
    if fmode != mode:
443 b81b3c96 René Nussbaumer
      logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
444 b81b3c96 René Nussbaumer
      _chmod_fn(path, mode)
445 b81b3c96 René Nussbaumer
446 b81b3c96 René Nussbaumer
    if max(uid, gid) > -1:
447 b81b3c96 René Nussbaumer
      fuid = st[stat.ST_UID]
448 b81b3c96 René Nussbaumer
      fgid = st[stat.ST_GID]
449 b81b3c96 René Nussbaumer
      if fuid != uid or fgid != gid:
450 b81b3c96 René Nussbaumer
        logging.debug("Changing owner of %s from UID %s/GID %s to"
451 b81b3c96 René Nussbaumer
                      " UID %s/GID %s", path, fuid, fgid, uid, gid)
452 b81b3c96 René Nussbaumer
        _chown_fn(path, uid, gid)
453 b81b3c96 René Nussbaumer
  except EnvironmentError, err:
454 b81b3c96 René Nussbaumer
    if err.errno == errno.ENOENT:
455 b81b3c96 René Nussbaumer
      if must_exist:
456 b81b3c96 René Nussbaumer
        raise errors.GenericError("Path %s should exist, but does not" % path)
457 b81b3c96 René Nussbaumer
    else:
458 b81b3c96 René Nussbaumer
      raise errors.GenericError("Error while changing permissions on %s: %s" %
459 b81b3c96 René Nussbaumer
                                (path, err))
460 b81b3c96 René Nussbaumer
461 b81b3c96 René Nussbaumer
462 b81b3c96 René Nussbaumer
def MakeDirWithPerm(path, mode, uid, gid, _lstat_fn=os.lstat,
463 b81b3c96 René Nussbaumer
                    _mkdir_fn=os.mkdir, _perm_fn=EnforcePermission):
464 b81b3c96 René Nussbaumer
  """Enforces that given path is a dir and has given mode, uid and gid set.
465 b81b3c96 René Nussbaumer

466 b81b3c96 René Nussbaumer
  @param path: The path to the file
467 b81b3c96 René Nussbaumer
  @param mode: The mode of the file
468 b81b3c96 René Nussbaumer
  @param uid: The uid of the owner of this file
469 b81b3c96 René Nussbaumer
  @param gid: The gid of the owner of this file
470 b81b3c96 René Nussbaumer
  @param _lstat_fn: Stat function to use (unittest only)
471 b81b3c96 René Nussbaumer
  @param _mkdir_fn: mkdir function to use (unittest only)
472 b81b3c96 René Nussbaumer
  @param _perm_fn: permission setter function to use (unittest only)
473 b81b3c96 René Nussbaumer

474 b81b3c96 René Nussbaumer
  """
475 b81b3c96 René Nussbaumer
  logging.debug("Checking directory %s", path)
476 b81b3c96 René Nussbaumer
  try:
477 b81b3c96 René Nussbaumer
    # We don't want to follow symlinks
478 b81b3c96 René Nussbaumer
    st = _lstat_fn(path)
479 b81b3c96 René Nussbaumer
  except EnvironmentError, err:
480 b81b3c96 René Nussbaumer
    if err.errno != errno.ENOENT:
481 b81b3c96 René Nussbaumer
      raise errors.GenericError("stat(2) on %s failed: %s" % (path, err))
482 b81b3c96 René Nussbaumer
    _mkdir_fn(path)
483 b81b3c96 René Nussbaumer
  else:
484 b81b3c96 René Nussbaumer
    if not stat.S_ISDIR(st[stat.ST_MODE]):
485 b81b3c96 René Nussbaumer
      raise errors.GenericError(("Path %s is expected to be a directory, but "
486 b81b3c96 René Nussbaumer
                                 "isn't") % path)
487 b81b3c96 René Nussbaumer
488 b81b3c96 René Nussbaumer
  _perm_fn(path, mode, uid=uid, gid=gid)
489 b81b3c96 René Nussbaumer
490 b81b3c96 René Nussbaumer
491 3865ca48 Michael Hanselmann
def Makedirs(path, mode=0750):
492 3865ca48 Michael Hanselmann
  """Super-mkdir; create a leaf directory and all intermediate ones.
493 3865ca48 Michael Hanselmann

494 3865ca48 Michael Hanselmann
  This is a wrapper around C{os.makedirs} adding error handling not implemented
495 3865ca48 Michael Hanselmann
  before Python 2.5.
496 3865ca48 Michael Hanselmann

497 3865ca48 Michael Hanselmann
  """
498 3865ca48 Michael Hanselmann
  try:
499 3865ca48 Michael Hanselmann
    os.makedirs(path, mode)
500 3865ca48 Michael Hanselmann
  except OSError, err:
501 3865ca48 Michael Hanselmann
    # Ignore EEXIST. This is only handled in os.makedirs as included in
502 3865ca48 Michael Hanselmann
    # Python 2.5 and above.
503 3865ca48 Michael Hanselmann
    if err.errno != errno.EEXIST or not os.path.exists(path):
504 3865ca48 Michael Hanselmann
      raise
505 3865ca48 Michael Hanselmann
506 3865ca48 Michael Hanselmann
507 3865ca48 Michael Hanselmann
def TimestampForFilename():
508 3865ca48 Michael Hanselmann
  """Returns the current time formatted for filenames.
509 3865ca48 Michael Hanselmann

510 3865ca48 Michael Hanselmann
  The format doesn't contain colons as some shells and applications treat them
511 3865ca48 Michael Hanselmann
  as separators. Uses the local timezone.
512 3865ca48 Michael Hanselmann

513 3865ca48 Michael Hanselmann
  """
514 3865ca48 Michael Hanselmann
  return time.strftime("%Y-%m-%d_%H_%M_%S")
515 3865ca48 Michael Hanselmann
516 3865ca48 Michael Hanselmann
517 3865ca48 Michael Hanselmann
def CreateBackup(file_name):
518 3865ca48 Michael Hanselmann
  """Creates a backup of a file.
519 3865ca48 Michael Hanselmann

520 3865ca48 Michael Hanselmann
  @type file_name: str
521 3865ca48 Michael Hanselmann
  @param file_name: file to be backed up
522 3865ca48 Michael Hanselmann
  @rtype: str
523 3865ca48 Michael Hanselmann
  @return: the path to the newly created backup
524 3865ca48 Michael Hanselmann
  @raise errors.ProgrammerError: for invalid file names
525 3865ca48 Michael Hanselmann

526 3865ca48 Michael Hanselmann
  """
527 3865ca48 Michael Hanselmann
  if not os.path.isfile(file_name):
528 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
529 5ae4945a Iustin Pop
                                 file_name)
530 3865ca48 Michael Hanselmann
531 3865ca48 Michael Hanselmann
  prefix = ("%s.backup-%s." %
532 3865ca48 Michael Hanselmann
            (os.path.basename(file_name), TimestampForFilename()))
533 3865ca48 Michael Hanselmann
  dir_name = os.path.dirname(file_name)
534 3865ca48 Michael Hanselmann
535 d0c8c01d Iustin Pop
  fsrc = open(file_name, "rb")
536 3865ca48 Michael Hanselmann
  try:
537 3865ca48 Michael Hanselmann
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
538 d0c8c01d Iustin Pop
    fdst = os.fdopen(fd, "wb")
539 3865ca48 Michael Hanselmann
    try:
540 3865ca48 Michael Hanselmann
      logging.debug("Backing up %s at %s", file_name, backup_name)
541 3865ca48 Michael Hanselmann
      shutil.copyfileobj(fsrc, fdst)
542 3865ca48 Michael Hanselmann
    finally:
543 3865ca48 Michael Hanselmann
      fdst.close()
544 3865ca48 Michael Hanselmann
  finally:
545 3865ca48 Michael Hanselmann
    fsrc.close()
546 3865ca48 Michael Hanselmann
547 3865ca48 Michael Hanselmann
  return backup_name
548 3865ca48 Michael Hanselmann
549 3865ca48 Michael Hanselmann
550 2dbc6857 Michael Hanselmann
def ListVisibleFiles(path, _is_mountpoint=os.path.ismount):
551 3865ca48 Michael Hanselmann
  """Returns a list of visible files in a directory.
552 3865ca48 Michael Hanselmann

553 3865ca48 Michael Hanselmann
  @type path: str
554 3865ca48 Michael Hanselmann
  @param path: the directory to enumerate
555 3865ca48 Michael Hanselmann
  @rtype: list
556 3865ca48 Michael Hanselmann
  @return: the list of all files not starting with a dot
557 3865ca48 Michael Hanselmann
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
558 3865ca48 Michael Hanselmann

559 3865ca48 Michael Hanselmann
  """
560 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(path):
561 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
562 3865ca48 Michael Hanselmann
                                 " absolute/normalized: '%s'" % path)
563 2dbc6857 Michael Hanselmann
564 2dbc6857 Michael Hanselmann
  mountpoint = _is_mountpoint(path)
565 2dbc6857 Michael Hanselmann
566 2dbc6857 Michael Hanselmann
  def fn(name):
567 2dbc6857 Michael Hanselmann
    """File name filter.
568 2dbc6857 Michael Hanselmann

569 2dbc6857 Michael Hanselmann
    Ignores files starting with a dot (".") as by Unix convention they're
570 2dbc6857 Michael Hanselmann
    considered hidden. The "lost+found" directory found at the root of some
571 2dbc6857 Michael Hanselmann
    filesystems is also hidden.
572 2dbc6857 Michael Hanselmann

573 2dbc6857 Michael Hanselmann
    """
574 2dbc6857 Michael Hanselmann
    return not (name.startswith(".") or
575 2dbc6857 Michael Hanselmann
                (mountpoint and name == _LOST_AND_FOUND and
576 2dbc6857 Michael Hanselmann
                 os.path.isdir(os.path.join(path, name))))
577 2dbc6857 Michael Hanselmann
578 2dbc6857 Michael Hanselmann
  return filter(fn, os.listdir(path))
579 3865ca48 Michael Hanselmann
580 3865ca48 Michael Hanselmann
581 3865ca48 Michael Hanselmann
def EnsureDirs(dirs):
582 3865ca48 Michael Hanselmann
  """Make required directories, if they don't exist.
583 3865ca48 Michael Hanselmann

584 3865ca48 Michael Hanselmann
  @param dirs: list of tuples (dir_name, dir_mode)
585 3865ca48 Michael Hanselmann
  @type dirs: list of (string, integer)
586 3865ca48 Michael Hanselmann

587 3865ca48 Michael Hanselmann
  """
588 3865ca48 Michael Hanselmann
  for dir_name, dir_mode in dirs:
589 3865ca48 Michael Hanselmann
    try:
590 3865ca48 Michael Hanselmann
      os.mkdir(dir_name, dir_mode)
591 3865ca48 Michael Hanselmann
    except EnvironmentError, err:
592 3865ca48 Michael Hanselmann
      if err.errno != errno.EEXIST:
593 3865ca48 Michael Hanselmann
        raise errors.GenericError("Cannot create needed directory"
594 3865ca48 Michael Hanselmann
                                  " '%s': %s" % (dir_name, err))
595 3865ca48 Michael Hanselmann
    try:
596 3865ca48 Michael Hanselmann
      os.chmod(dir_name, dir_mode)
597 3865ca48 Michael Hanselmann
    except EnvironmentError, err:
598 3865ca48 Michael Hanselmann
      raise errors.GenericError("Cannot change directory permissions on"
599 3865ca48 Michael Hanselmann
                                " '%s': %s" % (dir_name, err))
600 3865ca48 Michael Hanselmann
    if not os.path.isdir(dir_name):
601 3865ca48 Michael Hanselmann
      raise errors.GenericError("%s is not a directory" % dir_name)
602 3865ca48 Michael Hanselmann
603 3865ca48 Michael Hanselmann
604 3865ca48 Michael Hanselmann
def FindFile(name, search_path, test=os.path.exists):
605 3865ca48 Michael Hanselmann
  """Look for a filesystem object in a given path.
606 3865ca48 Michael Hanselmann

607 3865ca48 Michael Hanselmann
  This is an abstract method to search for filesystem object (files,
608 3865ca48 Michael Hanselmann
  dirs) under a given search path.
609 3865ca48 Michael Hanselmann

610 3865ca48 Michael Hanselmann
  @type name: str
611 3865ca48 Michael Hanselmann
  @param name: the name to look for
612 3865ca48 Michael Hanselmann
  @type search_path: str
613 3865ca48 Michael Hanselmann
  @param search_path: location to start at
614 3865ca48 Michael Hanselmann
  @type test: callable
615 3865ca48 Michael Hanselmann
  @param test: a function taking one argument that should return True
616 3865ca48 Michael Hanselmann
      if the a given object is valid; the default value is
617 3865ca48 Michael Hanselmann
      os.path.exists, causing only existing files to be returned
618 3865ca48 Michael Hanselmann
  @rtype: str or None
619 3865ca48 Michael Hanselmann
  @return: full path to the object if found, None otherwise
620 3865ca48 Michael Hanselmann

621 3865ca48 Michael Hanselmann
  """
622 3865ca48 Michael Hanselmann
  # validate the filename mask
623 3865ca48 Michael Hanselmann
  if constants.EXT_PLUGIN_MASK.match(name) is None:
624 3865ca48 Michael Hanselmann
    logging.critical("Invalid value passed for external script name: '%s'",
625 3865ca48 Michael Hanselmann
                     name)
626 3865ca48 Michael Hanselmann
    return None
627 3865ca48 Michael Hanselmann
628 3865ca48 Michael Hanselmann
  for dir_name in search_path:
629 3865ca48 Michael Hanselmann
    # FIXME: investigate switch to PathJoin
630 3865ca48 Michael Hanselmann
    item_name = os.path.sep.join([dir_name, name])
631 3865ca48 Michael Hanselmann
    # check the user test and that we're indeed resolving to the given
632 3865ca48 Michael Hanselmann
    # basename
633 3865ca48 Michael Hanselmann
    if test(item_name) and os.path.basename(item_name) == name:
634 3865ca48 Michael Hanselmann
      return item_name
635 3865ca48 Michael Hanselmann
  return None
636 3865ca48 Michael Hanselmann
637 3865ca48 Michael Hanselmann
638 3865ca48 Michael Hanselmann
def IsNormAbsPath(path):
639 3865ca48 Michael Hanselmann
  """Check whether a path is absolute and also normalized
640 3865ca48 Michael Hanselmann

641 3865ca48 Michael Hanselmann
  This avoids things like /dir/../../other/path to be valid.
642 3865ca48 Michael Hanselmann

643 3865ca48 Michael Hanselmann
  """
644 3865ca48 Michael Hanselmann
  return os.path.normpath(path) == path and os.path.isabs(path)
645 3865ca48 Michael Hanselmann
646 3865ca48 Michael Hanselmann
647 fb17bebd René Nussbaumer
def IsBelowDir(root, other_path):
648 fb17bebd René Nussbaumer
  """Check whether a path is below a root dir.
649 fb17bebd René Nussbaumer

650 2826897c Michael Hanselmann
  This works around the nasty byte-byte comparison of commonprefix.
651 fb17bebd René Nussbaumer

652 fb17bebd René Nussbaumer
  """
653 fb17bebd René Nussbaumer
  if not (os.path.isabs(root) and os.path.isabs(other_path)):
654 fb17bebd René Nussbaumer
    raise ValueError("Provided paths '%s' and '%s' are not absolute" %
655 fb17bebd René Nussbaumer
                     (root, other_path))
656 2826897c Michael Hanselmann
657 2826897c Michael Hanselmann
  norm_other = os.path.normpath(other_path)
658 2826897c Michael Hanselmann
659 2826897c Michael Hanselmann
  if norm_other == os.sep:
660 2826897c Michael Hanselmann
    # The root directory can never be below another path
661 2826897c Michael Hanselmann
    return False
662 2826897c Michael Hanselmann
663 2826897c Michael Hanselmann
  norm_root = os.path.normpath(root)
664 2826897c Michael Hanselmann
665 2826897c Michael Hanselmann
  if norm_root == os.sep:
666 2826897c Michael Hanselmann
    # This is the root directory, no need to add another slash
667 2826897c Michael Hanselmann
    prepared_root = norm_root
668 2826897c Michael Hanselmann
  else:
669 2826897c Michael Hanselmann
    prepared_root = "%s%s" % (norm_root, os.sep)
670 2826897c Michael Hanselmann
671 2826897c Michael Hanselmann
  return os.path.commonprefix([prepared_root, norm_other]) == prepared_root
672 fb17bebd René Nussbaumer
673 fb17bebd René Nussbaumer
674 364e1664 Jose A. Lopes
URL_RE = re.compile(r'(https?|ftps?)://')
675 364e1664 Jose A. Lopes
676 364e1664 Jose A. Lopes
677 364e1664 Jose A. Lopes
def IsUrl(path):
678 364e1664 Jose A. Lopes
  """Check whether a path is a HTTP URL.
679 f15a6b03 Jose A. Lopes

680 f15a6b03 Jose A. Lopes
  """
681 364e1664 Jose A. Lopes
  return URL_RE.match(path)
682 f15a6b03 Jose A. Lopes
683 f15a6b03 Jose A. Lopes
684 3865ca48 Michael Hanselmann
def PathJoin(*args):
685 3865ca48 Michael Hanselmann
  """Safe-join a list of path components.
686 3865ca48 Michael Hanselmann

687 3865ca48 Michael Hanselmann
  Requirements:
688 3865ca48 Michael Hanselmann
      - the first argument must be an absolute path
689 3865ca48 Michael Hanselmann
      - no component in the path must have backtracking (e.g. /../),
690 3865ca48 Michael Hanselmann
        since we check for normalization at the end
691 3865ca48 Michael Hanselmann

692 3865ca48 Michael Hanselmann
  @param args: the path components to be joined
693 3865ca48 Michael Hanselmann
  @raise ValueError: for invalid paths
694 3865ca48 Michael Hanselmann

695 3865ca48 Michael Hanselmann
  """
696 d2234191 Santi Raffa
  # ensure we're having at least two paths passed in
697 d2234191 Santi Raffa
  if len(args) <= 1:
698 d2234191 Santi Raffa
    raise errors.ProgrammerError("PathJoin requires two arguments")
699 3865ca48 Michael Hanselmann
  # ensure the first component is an absolute and normalized path name
700 3865ca48 Michael Hanselmann
  root = args[0]
701 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(root):
702 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
703 3865ca48 Michael Hanselmann
  result = os.path.join(*args)
704 3865ca48 Michael Hanselmann
  # ensure that the whole path is normalized
705 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(result):
706 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
707 3865ca48 Michael Hanselmann
  # check that we're still under the original prefix
708 cf00dba0 René Nussbaumer
  if not IsBelowDir(root, result):
709 3865ca48 Michael Hanselmann
    raise ValueError("Error: path joining resulted in different prefix"
710 cf00dba0 René Nussbaumer
                     " (%s != %s)" % (result, root))
711 3865ca48 Michael Hanselmann
  return result
712 3865ca48 Michael Hanselmann
713 3865ca48 Michael Hanselmann
714 3865ca48 Michael Hanselmann
def TailFile(fname, lines=20):
715 3865ca48 Michael Hanselmann
  """Return the last lines from a file.
716 3865ca48 Michael Hanselmann

717 3865ca48 Michael Hanselmann
  @note: this function will only read and parse the last 4KB of
718 3865ca48 Michael Hanselmann
      the file; if the lines are very long, it could be that less
719 3865ca48 Michael Hanselmann
      than the requested number of lines are returned
720 3865ca48 Michael Hanselmann

721 3865ca48 Michael Hanselmann
  @param fname: the file name
722 3865ca48 Michael Hanselmann
  @type lines: int
723 3865ca48 Michael Hanselmann
  @param lines: the (maximum) number of lines to return
724 3865ca48 Michael Hanselmann

725 3865ca48 Michael Hanselmann
  """
726 3865ca48 Michael Hanselmann
  fd = open(fname, "r")
727 3865ca48 Michael Hanselmann
  try:
728 3865ca48 Michael Hanselmann
    fd.seek(0, 2)
729 3865ca48 Michael Hanselmann
    pos = fd.tell()
730 e687ec01 Michael Hanselmann
    pos = max(0, pos - 4096)
731 3865ca48 Michael Hanselmann
    fd.seek(pos, 0)
732 3865ca48 Michael Hanselmann
    raw_data = fd.read()
733 3865ca48 Michael Hanselmann
  finally:
734 3865ca48 Michael Hanselmann
    fd.close()
735 3865ca48 Michael Hanselmann
736 3865ca48 Michael Hanselmann
  rows = raw_data.splitlines()
737 3865ca48 Michael Hanselmann
  return rows[-lines:]
738 3865ca48 Michael Hanselmann
739 3865ca48 Michael Hanselmann
740 3865ca48 Michael Hanselmann
def BytesToMebibyte(value):
741 3865ca48 Michael Hanselmann
  """Converts bytes to mebibytes.
742 3865ca48 Michael Hanselmann

743 3865ca48 Michael Hanselmann
  @type value: int
744 3865ca48 Michael Hanselmann
  @param value: Value in bytes
745 3865ca48 Michael Hanselmann
  @rtype: int
746 3865ca48 Michael Hanselmann
  @return: Value in mebibytes
747 3865ca48 Michael Hanselmann

748 3865ca48 Michael Hanselmann
  """
749 3865ca48 Michael Hanselmann
  return int(round(value / (1024.0 * 1024.0), 0))
750 3865ca48 Michael Hanselmann
751 3865ca48 Michael Hanselmann
752 3865ca48 Michael Hanselmann
def CalculateDirectorySize(path):
753 3865ca48 Michael Hanselmann
  """Calculates the size of a directory recursively.
754 3865ca48 Michael Hanselmann

755 3865ca48 Michael Hanselmann
  @type path: string
756 3865ca48 Michael Hanselmann
  @param path: Path to directory
757 3865ca48 Michael Hanselmann
  @rtype: int
758 3865ca48 Michael Hanselmann
  @return: Size in mebibytes
759 3865ca48 Michael Hanselmann

760 3865ca48 Michael Hanselmann
  """
761 3865ca48 Michael Hanselmann
  size = 0
762 3865ca48 Michael Hanselmann
763 3865ca48 Michael Hanselmann
  for (curpath, _, files) in os.walk(path):
764 3865ca48 Michael Hanselmann
    for filename in files:
765 3865ca48 Michael Hanselmann
      st = os.lstat(PathJoin(curpath, filename))
766 3865ca48 Michael Hanselmann
      size += st.st_size
767 3865ca48 Michael Hanselmann
768 3865ca48 Michael Hanselmann
  return BytesToMebibyte(size)
769 3865ca48 Michael Hanselmann
770 3865ca48 Michael Hanselmann
771 3865ca48 Michael Hanselmann
def GetFilesystemStats(path):
772 3865ca48 Michael Hanselmann
  """Returns the total and free space on a filesystem.
773 3865ca48 Michael Hanselmann

774 3865ca48 Michael Hanselmann
  @type path: string
775 3865ca48 Michael Hanselmann
  @param path: Path on filesystem to be examined
776 3865ca48 Michael Hanselmann
  @rtype: int
777 3865ca48 Michael Hanselmann
  @return: tuple of (Total space, Free space) in mebibytes
778 3865ca48 Michael Hanselmann

779 3865ca48 Michael Hanselmann
  """
780 3865ca48 Michael Hanselmann
  st = os.statvfs(path)
781 3865ca48 Michael Hanselmann
782 3865ca48 Michael Hanselmann
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
783 3865ca48 Michael Hanselmann
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
784 3865ca48 Michael Hanselmann
  return (tsize, fsize)
785 3865ca48 Michael Hanselmann
786 3865ca48 Michael Hanselmann
787 3865ca48 Michael Hanselmann
def ReadPidFile(pidfile):
788 3865ca48 Michael Hanselmann
  """Read a pid from a file.
789 3865ca48 Michael Hanselmann

790 3865ca48 Michael Hanselmann
  @type  pidfile: string
791 3865ca48 Michael Hanselmann
  @param pidfile: path to the file containing the pid
792 3865ca48 Michael Hanselmann
  @rtype: int
793 3865ca48 Michael Hanselmann
  @return: The process id, if the file exists and contains a valid PID,
794 3865ca48 Michael Hanselmann
           otherwise 0
795 3865ca48 Michael Hanselmann

796 3865ca48 Michael Hanselmann
  """
797 3865ca48 Michael Hanselmann
  try:
798 3865ca48 Michael Hanselmann
    raw_data = ReadOneLineFile(pidfile)
799 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
800 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
801 3865ca48 Michael Hanselmann
      logging.exception("Can't read pid file")
802 3865ca48 Michael Hanselmann
    return 0
803 3865ca48 Michael Hanselmann
804 b6522276 Michael Hanselmann
  return _ParsePidFileContents(raw_data)
805 b6522276 Michael Hanselmann
806 b6522276 Michael Hanselmann
807 b6522276 Michael Hanselmann
def _ParsePidFileContents(data):
808 b6522276 Michael Hanselmann
  """Tries to extract a process ID from a PID file's content.
809 b6522276 Michael Hanselmann

810 b6522276 Michael Hanselmann
  @type data: string
811 b6522276 Michael Hanselmann
  @rtype: int
812 b6522276 Michael Hanselmann
  @return: Zero if nothing could be read, PID otherwise
813 b6522276 Michael Hanselmann

814 b6522276 Michael Hanselmann
  """
815 3865ca48 Michael Hanselmann
  try:
816 b6522276 Michael Hanselmann
    pid = int(data)
817 b6522276 Michael Hanselmann
  except (TypeError, ValueError):
818 3865ca48 Michael Hanselmann
    logging.info("Can't parse pid file contents", exc_info=True)
819 3865ca48 Michael Hanselmann
    return 0
820 b6522276 Michael Hanselmann
  else:
821 b6522276 Michael Hanselmann
    return pid
822 3865ca48 Michael Hanselmann
823 3865ca48 Michael Hanselmann
824 3865ca48 Michael Hanselmann
def ReadLockedPidFile(path):
825 3865ca48 Michael Hanselmann
  """Reads a locked PID file.
826 3865ca48 Michael Hanselmann

827 a4ccecf6 Michael Hanselmann
  This can be used together with L{utils.process.StartDaemon}.
828 3865ca48 Michael Hanselmann

829 3865ca48 Michael Hanselmann
  @type path: string
830 3865ca48 Michael Hanselmann
  @param path: Path to PID file
831 3865ca48 Michael Hanselmann
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
832 3865ca48 Michael Hanselmann

833 3865ca48 Michael Hanselmann
  """
834 3865ca48 Michael Hanselmann
  try:
835 3865ca48 Michael Hanselmann
    fd = os.open(path, os.O_RDONLY)
836 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
837 3865ca48 Michael Hanselmann
    if err.errno == errno.ENOENT:
838 3865ca48 Michael Hanselmann
      # PID file doesn't exist
839 3865ca48 Michael Hanselmann
      return None
840 3865ca48 Michael Hanselmann
    raise
841 3865ca48 Michael Hanselmann
842 3865ca48 Michael Hanselmann
  try:
843 3865ca48 Michael Hanselmann
    try:
844 3865ca48 Michael Hanselmann
      # Try to acquire lock
845 3865ca48 Michael Hanselmann
      filelock.LockFile(fd)
846 3865ca48 Michael Hanselmann
    except errors.LockError:
847 3865ca48 Michael Hanselmann
      # Couldn't lock, daemon is running
848 3865ca48 Michael Hanselmann
      return int(os.read(fd, 100))
849 3865ca48 Michael Hanselmann
  finally:
850 3865ca48 Michael Hanselmann
    os.close(fd)
851 3865ca48 Michael Hanselmann
852 3865ca48 Michael Hanselmann
  return None
853 3865ca48 Michael Hanselmann
854 3865ca48 Michael Hanselmann
855 0232b768 Michael Hanselmann
def _SplitSshKey(key):
856 0232b768 Michael Hanselmann
  """Splits a line for SSH's C{authorized_keys} file.
857 0232b768 Michael Hanselmann

858 0232b768 Michael Hanselmann
  If the line has no options (e.g. no C{command="..."}), only the significant
859 0232b768 Michael Hanselmann
  parts, the key type and its hash, are used. Otherwise the whole line is used
860 0232b768 Michael Hanselmann
  (split at whitespace).
861 0232b768 Michael Hanselmann

862 0232b768 Michael Hanselmann
  @type key: string
863 0232b768 Michael Hanselmann
  @param key: Key line
864 0232b768 Michael Hanselmann
  @rtype: tuple
865 0232b768 Michael Hanselmann

866 0232b768 Michael Hanselmann
  """
867 0232b768 Michael Hanselmann
  parts = key.split()
868 0232b768 Michael Hanselmann
869 d12b9f66 Michael Hanselmann
  if parts and parts[0] in constants.SSHAK_ALL:
870 0232b768 Michael Hanselmann
    # If the key has no options in front of it, we only want the significant
871 0232b768 Michael Hanselmann
    # fields
872 0232b768 Michael Hanselmann
    return (False, parts[:2])
873 0232b768 Michael Hanselmann
  else:
874 0232b768 Michael Hanselmann
    # Can't properly split the line, so use everything
875 0232b768 Michael Hanselmann
    return (True, parts)
876 0232b768 Michael Hanselmann
877 0232b768 Michael Hanselmann
878 3865ca48 Michael Hanselmann
def AddAuthorizedKey(file_obj, key):
879 3865ca48 Michael Hanselmann
  """Adds an SSH public key to an authorized_keys file.
880 3865ca48 Michael Hanselmann

881 3865ca48 Michael Hanselmann
  @type file_obj: str or file handle
882 3865ca48 Michael Hanselmann
  @param file_obj: path to authorized_keys file
883 3865ca48 Michael Hanselmann
  @type key: str
884 3865ca48 Michael Hanselmann
  @param key: string containing key
885 3865ca48 Michael Hanselmann

886 3865ca48 Michael Hanselmann
  """
887 0232b768 Michael Hanselmann
  key_fields = _SplitSshKey(key)
888 3865ca48 Michael Hanselmann
889 3865ca48 Michael Hanselmann
  if isinstance(file_obj, basestring):
890 d0c8c01d Iustin Pop
    f = open(file_obj, "a+")
891 3865ca48 Michael Hanselmann
  else:
892 3865ca48 Michael Hanselmann
    f = file_obj
893 3865ca48 Michael Hanselmann
894 3865ca48 Michael Hanselmann
  try:
895 3865ca48 Michael Hanselmann
    nl = True
896 3865ca48 Michael Hanselmann
    for line in f:
897 3865ca48 Michael Hanselmann
      # Ignore whitespace changes
898 0232b768 Michael Hanselmann
      if _SplitSshKey(line) == key_fields:
899 3865ca48 Michael Hanselmann
        break
900 d0c8c01d Iustin Pop
      nl = line.endswith("\n")
901 3865ca48 Michael Hanselmann
    else:
902 3865ca48 Michael Hanselmann
      if not nl:
903 3865ca48 Michael Hanselmann
        f.write("\n")
904 d0c8c01d Iustin Pop
      f.write(key.rstrip("\r\n"))
905 3865ca48 Michael Hanselmann
      f.write("\n")
906 3865ca48 Michael Hanselmann
      f.flush()
907 3865ca48 Michael Hanselmann
  finally:
908 3865ca48 Michael Hanselmann
    f.close()
909 3865ca48 Michael Hanselmann
910 3865ca48 Michael Hanselmann
911 3865ca48 Michael Hanselmann
def RemoveAuthorizedKey(file_name, key):
912 3865ca48 Michael Hanselmann
  """Removes an SSH public key from an authorized_keys file.
913 3865ca48 Michael Hanselmann

914 3865ca48 Michael Hanselmann
  @type file_name: str
915 3865ca48 Michael Hanselmann
  @param file_name: path to authorized_keys file
916 3865ca48 Michael Hanselmann
  @type key: str
917 3865ca48 Michael Hanselmann
  @param key: string containing key
918 3865ca48 Michael Hanselmann

919 3865ca48 Michael Hanselmann
  """
920 0232b768 Michael Hanselmann
  key_fields = _SplitSshKey(key)
921 3865ca48 Michael Hanselmann
922 3865ca48 Michael Hanselmann
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
923 3865ca48 Michael Hanselmann
  try:
924 d0c8c01d Iustin Pop
    out = os.fdopen(fd, "w")
925 3865ca48 Michael Hanselmann
    try:
926 d0c8c01d Iustin Pop
      f = open(file_name, "r")
927 3865ca48 Michael Hanselmann
      try:
928 3865ca48 Michael Hanselmann
        for line in f:
929 3865ca48 Michael Hanselmann
          # Ignore whitespace changes while comparing lines
930 0232b768 Michael Hanselmann
          if _SplitSshKey(line) != key_fields:
931 3865ca48 Michael Hanselmann
            out.write(line)
932 3865ca48 Michael Hanselmann
933 3865ca48 Michael Hanselmann
        out.flush()
934 3865ca48 Michael Hanselmann
        os.rename(tmpname, file_name)
935 3865ca48 Michael Hanselmann
      finally:
936 3865ca48 Michael Hanselmann
        f.close()
937 3865ca48 Michael Hanselmann
    finally:
938 3865ca48 Michael Hanselmann
      out.close()
939 3865ca48 Michael Hanselmann
  except:
940 3865ca48 Michael Hanselmann
    RemoveFile(tmpname)
941 3865ca48 Michael Hanselmann
    raise
942 3865ca48 Michael Hanselmann
943 3865ca48 Michael Hanselmann
944 3865ca48 Michael Hanselmann
def DaemonPidFileName(name):
945 3865ca48 Michael Hanselmann
  """Compute a ganeti pid file absolute path
946 3865ca48 Michael Hanselmann

947 3865ca48 Michael Hanselmann
  @type name: str
948 3865ca48 Michael Hanselmann
  @param name: the daemon name
949 3865ca48 Michael Hanselmann
  @rtype: str
950 3865ca48 Michael Hanselmann
  @return: the full path to the pidfile corresponding to the given
951 3865ca48 Michael Hanselmann
      daemon name
952 3865ca48 Michael Hanselmann

953 3865ca48 Michael Hanselmann
  """
954 111a7d04 Michael Hanselmann
  return PathJoin(pathutils.RUN_DIR, "%s.pid" % name)
955 3865ca48 Michael Hanselmann
956 3865ca48 Michael Hanselmann
957 3865ca48 Michael Hanselmann
def WritePidFile(pidfile):
958 3865ca48 Michael Hanselmann
  """Write the current process pidfile.
959 3865ca48 Michael Hanselmann

960 3865ca48 Michael Hanselmann
  @type pidfile: string
961 3865ca48 Michael Hanselmann
  @param pidfile: the path to the file to be written
962 3865ca48 Michael Hanselmann
  @raise errors.LockError: if the pid file already exists and
963 3865ca48 Michael Hanselmann
      points to a live process
964 3865ca48 Michael Hanselmann
  @rtype: int
965 3865ca48 Michael Hanselmann
  @return: the file descriptor of the lock file; do not close this unless
966 3865ca48 Michael Hanselmann
      you want to unlock the pid file
967 3865ca48 Michael Hanselmann

968 3865ca48 Michael Hanselmann
  """
969 3865ca48 Michael Hanselmann
  # We don't rename nor truncate the file to not drop locks under
970 3865ca48 Michael Hanselmann
  # existing processes
971 b6522276 Michael Hanselmann
  fd_pidfile = os.open(pidfile, os.O_RDWR | os.O_CREAT, 0600)
972 3865ca48 Michael Hanselmann
973 3865ca48 Michael Hanselmann
  # Lock the PID file (and fail if not possible to do so). Any code
974 3865ca48 Michael Hanselmann
  # wanting to send a signal to the daemon should try to lock the PID
975 3865ca48 Michael Hanselmann
  # file before reading it. If acquiring the lock succeeds, the daemon is
976 3865ca48 Michael Hanselmann
  # no longer running and the signal should not be sent.
977 b6522276 Michael Hanselmann
  try:
978 b6522276 Michael Hanselmann
    filelock.LockFile(fd_pidfile)
979 b6522276 Michael Hanselmann
  except errors.LockError:
980 b6522276 Michael Hanselmann
    msg = ["PID file '%s' is already locked by another process" % pidfile]
981 b6522276 Michael Hanselmann
    # Try to read PID file
982 b6522276 Michael Hanselmann
    pid = _ParsePidFileContents(os.read(fd_pidfile, 100))
983 b6522276 Michael Hanselmann
    if pid > 0:
984 b6522276 Michael Hanselmann
      msg.append(", PID read from file is %s" % pid)
985 b6522276 Michael Hanselmann
    raise errors.PidFileLockError("".join(msg))
986 3865ca48 Michael Hanselmann
987 3865ca48 Michael Hanselmann
  os.write(fd_pidfile, "%d\n" % os.getpid())
988 3865ca48 Michael Hanselmann
989 3865ca48 Michael Hanselmann
  return fd_pidfile
990 3865ca48 Michael Hanselmann
991 3865ca48 Michael Hanselmann
992 3865ca48 Michael Hanselmann
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
993 3865ca48 Michael Hanselmann
  """Reads the watcher pause file.
994 3865ca48 Michael Hanselmann

995 3865ca48 Michael Hanselmann
  @type filename: string
996 3865ca48 Michael Hanselmann
  @param filename: Path to watcher pause file
997 3865ca48 Michael Hanselmann
  @type now: None, float or int
998 3865ca48 Michael Hanselmann
  @param now: Current time as Unix timestamp
999 3865ca48 Michael Hanselmann
  @type remove_after: int
1000 3865ca48 Michael Hanselmann
  @param remove_after: Remove watcher pause file after specified amount of
1001 3865ca48 Michael Hanselmann
    seconds past the pause end time
1002 3865ca48 Michael Hanselmann

1003 3865ca48 Michael Hanselmann
  """
1004 3865ca48 Michael Hanselmann
  if now is None:
1005 3865ca48 Michael Hanselmann
    now = time.time()
1006 3865ca48 Michael Hanselmann
1007 3865ca48 Michael Hanselmann
  try:
1008 3865ca48 Michael Hanselmann
    value = ReadFile(filename)
1009 3865ca48 Michael Hanselmann
  except IOError, err:
1010 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
1011 3865ca48 Michael Hanselmann
      raise
1012 3865ca48 Michael Hanselmann
    value = None
1013 3865ca48 Michael Hanselmann
1014 3865ca48 Michael Hanselmann
  if value is not None:
1015 3865ca48 Michael Hanselmann
    try:
1016 3865ca48 Michael Hanselmann
      value = int(value)
1017 3865ca48 Michael Hanselmann
    except ValueError:
1018 3865ca48 Michael Hanselmann
      logging.warning(("Watcher pause file (%s) contains invalid value,"
1019 3865ca48 Michael Hanselmann
                       " removing it"), filename)
1020 3865ca48 Michael Hanselmann
      RemoveFile(filename)
1021 3865ca48 Michael Hanselmann
      value = None
1022 3865ca48 Michael Hanselmann
1023 3865ca48 Michael Hanselmann
    if value is not None:
1024 3865ca48 Michael Hanselmann
      # Remove file if it's outdated
1025 3865ca48 Michael Hanselmann
      if now > (value + remove_after):
1026 3865ca48 Michael Hanselmann
        RemoveFile(filename)
1027 3865ca48 Michael Hanselmann
        value = None
1028 3865ca48 Michael Hanselmann
1029 3865ca48 Michael Hanselmann
      elif now > value:
1030 3865ca48 Michael Hanselmann
        value = None
1031 3865ca48 Michael Hanselmann
1032 3865ca48 Michael Hanselmann
  return value
1033 90e234a6 Michael Hanselmann
1034 90e234a6 Michael Hanselmann
1035 90e234a6 Michael Hanselmann
def NewUUID():
1036 90e234a6 Michael Hanselmann
  """Returns a random UUID.
1037 90e234a6 Michael Hanselmann

1038 90e234a6 Michael Hanselmann
  @note: This is a Linux-specific method as it uses the /proc
1039 90e234a6 Michael Hanselmann
      filesystem.
1040 90e234a6 Michael Hanselmann
  @rtype: str
1041 90e234a6 Michael Hanselmann

1042 90e234a6 Michael Hanselmann
  """
1043 80a0546b Michele Tartara
  return ReadFile(constants.RANDOM_UUID_FILE, size=128).rstrip("\n")
1044 0c1a5b1e Agata Murawska
1045 0c1a5b1e Agata Murawska
1046 0c1a5b1e Agata Murawska
class TemporaryFileManager(object):
1047 0c1a5b1e Agata Murawska
  """Stores the list of files to be deleted and removes them on demand.
1048 0c1a5b1e Agata Murawska

1049 0c1a5b1e Agata Murawska
  """
1050 0c1a5b1e Agata Murawska
1051 0c1a5b1e Agata Murawska
  def __init__(self):
1052 0c1a5b1e Agata Murawska
    self._files = []
1053 0c1a5b1e Agata Murawska
1054 0c1a5b1e Agata Murawska
  def __del__(self):
1055 0c1a5b1e Agata Murawska
    self.Cleanup()
1056 0c1a5b1e Agata Murawska
1057 0c1a5b1e Agata Murawska
  def Add(self, filename):
1058 0c1a5b1e Agata Murawska
    """Add file to list of files to be deleted.
1059 0c1a5b1e Agata Murawska

1060 0c1a5b1e Agata Murawska
    @type filename: string
1061 0c1a5b1e Agata Murawska
    @param filename: path to filename to be added
1062 0c1a5b1e Agata Murawska

1063 0c1a5b1e Agata Murawska
    """
1064 0c1a5b1e Agata Murawska
    self._files.append(filename)
1065 0c1a5b1e Agata Murawska
1066 0c1a5b1e Agata Murawska
  def Remove(self, filename):
1067 0c1a5b1e Agata Murawska
    """Remove file from list of files to be deleted.
1068 0c1a5b1e Agata Murawska

1069 0c1a5b1e Agata Murawska
    @type filename: string
1070 0c1a5b1e Agata Murawska
    @param filename: path to filename to be deleted
1071 0c1a5b1e Agata Murawska

1072 0c1a5b1e Agata Murawska
    """
1073 0c1a5b1e Agata Murawska
    self._files.remove(filename)
1074 0c1a5b1e Agata Murawska
1075 0c1a5b1e Agata Murawska
  def Cleanup(self):
1076 0c1a5b1e Agata Murawska
    """Delete all files marked for deletion
1077 0c1a5b1e Agata Murawska

1078 0c1a5b1e Agata Murawska
    """
1079 0c1a5b1e Agata Murawska
    while self._files:
1080 0c1a5b1e Agata Murawska
      RemoveFile(self._files.pop())
1081 fc84ef94 Michele Tartara
1082 fc84ef94 Michele Tartara
1083 fc84ef94 Michele Tartara
def IsUserInGroup(uid, gid):
1084 fc84ef94 Michele Tartara
  """Returns True if the user belongs to the group.
1085 fc84ef94 Michele Tartara

1086 fc84ef94 Michele Tartara
  @type uid: int
1087 fc84ef94 Michele Tartara
  @param uid: the user id
1088 fc84ef94 Michele Tartara
  @type gid: int
1089 fc84ef94 Michele Tartara
  @param gid: the group id
1090 fc84ef94 Michele Tartara
  @rtype: bool
1091 fc84ef94 Michele Tartara

1092 fc84ef94 Michele Tartara
  """
1093 fc84ef94 Michele Tartara
  user = pwd.getpwuid(uid)
1094 fc84ef94 Michele Tartara
  group = grp.getgrgid(gid)
1095 fc84ef94 Michele Tartara
  return user.pw_gid == gid or user.pw_name in group.gr_mem
1096 fc84ef94 Michele Tartara
1097 fc84ef94 Michele Tartara
1098 fc84ef94 Michele Tartara
def CanRead(username, filename):
1099 fc84ef94 Michele Tartara
  """Returns True if the user can access (read) the file.
1100 fc84ef94 Michele Tartara

1101 fc84ef94 Michele Tartara
  @type username: string
1102 fc84ef94 Michele Tartara
  @param username: the name of the user
1103 fc84ef94 Michele Tartara
  @type filename: string
1104 fc84ef94 Michele Tartara
  @param filename: the name of the file
1105 fc84ef94 Michele Tartara
  @rtype: bool
1106 fc84ef94 Michele Tartara

1107 fc84ef94 Michele Tartara
  """
1108 fc84ef94 Michele Tartara
  filestats = os.stat(filename)
1109 fc84ef94 Michele Tartara
  user = pwd.getpwnam(username)
1110 fc84ef94 Michele Tartara
  uid = user.pw_uid
1111 fc84ef94 Michele Tartara
  user_readable = filestats.st_mode & stat.S_IRUSR != 0
1112 fc84ef94 Michele Tartara
  group_readable = filestats.st_mode & stat.S_IRGRP != 0
1113 fc84ef94 Michele Tartara
  return ((filestats.st_uid == uid and user_readable)
1114 fc84ef94 Michele Tartara
          or (filestats.st_uid != uid and
1115 fc84ef94 Michele Tartara
              IsUserInGroup(uid, filestats.st_gid) and group_readable))