Statistics
| Branch: | Tag: | Revision:

root / lib / utils / io.py @ 5ae4945a

History | View | Annotate | Download (28.2 kB)

1 3865ca48 Michael Hanselmann
#
2 3865ca48 Michael Hanselmann
#
3 3865ca48 Michael Hanselmann
4 5ae4945a Iustin Pop
# Copyright (C) 2006, 2007, 2010, 2011, 2012 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 3865ca48 Michael Hanselmann
import logging
27 3865ca48 Michael Hanselmann
import shutil
28 3865ca48 Michael Hanselmann
import tempfile
29 3865ca48 Michael Hanselmann
import errno
30 3865ca48 Michael Hanselmann
import time
31 b81b3c96 René Nussbaumer
import stat
32 3865ca48 Michael Hanselmann
33 3865ca48 Michael Hanselmann
from ganeti import errors
34 3865ca48 Michael Hanselmann
from ganeti import constants
35 3865ca48 Michael Hanselmann
from ganeti.utils import filelock
36 3865ca48 Michael Hanselmann
37 3865ca48 Michael Hanselmann
38 90e234a6 Michael Hanselmann
#: Path generating random UUID
39 90e234a6 Michael Hanselmann
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
40 90e234a6 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 3865ca48 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

649 fb17bebd René Nussbaumer
  This works around the nasty byte-byte comparisation of commonprefix.
650 fb17bebd René Nussbaumer

651 fb17bebd René Nussbaumer
  """
652 fb17bebd René Nussbaumer
  if not (os.path.isabs(root) and os.path.isabs(other_path)):
653 fb17bebd René Nussbaumer
    raise ValueError("Provided paths '%s' and '%s' are not absolute" %
654 fb17bebd René Nussbaumer
                     (root, other_path))
655 fb17bebd René Nussbaumer
  prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
656 fb17bebd René Nussbaumer
  return os.path.commonprefix([prepared_root,
657 fb17bebd René Nussbaumer
                               os.path.normpath(other_path)]) == prepared_root
658 fb17bebd René Nussbaumer
659 fb17bebd René Nussbaumer
660 3865ca48 Michael Hanselmann
def PathJoin(*args):
661 3865ca48 Michael Hanselmann
  """Safe-join a list of path components.
662 3865ca48 Michael Hanselmann

663 3865ca48 Michael Hanselmann
  Requirements:
664 3865ca48 Michael Hanselmann
      - the first argument must be an absolute path
665 3865ca48 Michael Hanselmann
      - no component in the path must have backtracking (e.g. /../),
666 3865ca48 Michael Hanselmann
        since we check for normalization at the end
667 3865ca48 Michael Hanselmann

668 3865ca48 Michael Hanselmann
  @param args: the path components to be joined
669 3865ca48 Michael Hanselmann
  @raise ValueError: for invalid paths
670 3865ca48 Michael Hanselmann

671 3865ca48 Michael Hanselmann
  """
672 3865ca48 Michael Hanselmann
  # ensure we're having at least one path passed in
673 3865ca48 Michael Hanselmann
  assert args
674 3865ca48 Michael Hanselmann
  # ensure the first component is an absolute and normalized path name
675 3865ca48 Michael Hanselmann
  root = args[0]
676 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(root):
677 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
678 3865ca48 Michael Hanselmann
  result = os.path.join(*args)
679 3865ca48 Michael Hanselmann
  # ensure that the whole path is normalized
680 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(result):
681 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
682 3865ca48 Michael Hanselmann
  # check that we're still under the original prefix
683 cf00dba0 René Nussbaumer
  if not IsBelowDir(root, result):
684 3865ca48 Michael Hanselmann
    raise ValueError("Error: path joining resulted in different prefix"
685 cf00dba0 René Nussbaumer
                     " (%s != %s)" % (result, root))
686 3865ca48 Michael Hanselmann
  return result
687 3865ca48 Michael Hanselmann
688 3865ca48 Michael Hanselmann
689 3865ca48 Michael Hanselmann
def TailFile(fname, lines=20):
690 3865ca48 Michael Hanselmann
  """Return the last lines from a file.
691 3865ca48 Michael Hanselmann

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

696 3865ca48 Michael Hanselmann
  @param fname: the file name
697 3865ca48 Michael Hanselmann
  @type lines: int
698 3865ca48 Michael Hanselmann
  @param lines: the (maximum) number of lines to return
699 3865ca48 Michael Hanselmann

700 3865ca48 Michael Hanselmann
  """
701 3865ca48 Michael Hanselmann
  fd = open(fname, "r")
702 3865ca48 Michael Hanselmann
  try:
703 3865ca48 Michael Hanselmann
    fd.seek(0, 2)
704 3865ca48 Michael Hanselmann
    pos = fd.tell()
705 e687ec01 Michael Hanselmann
    pos = max(0, pos - 4096)
706 3865ca48 Michael Hanselmann
    fd.seek(pos, 0)
707 3865ca48 Michael Hanselmann
    raw_data = fd.read()
708 3865ca48 Michael Hanselmann
  finally:
709 3865ca48 Michael Hanselmann
    fd.close()
710 3865ca48 Michael Hanselmann
711 3865ca48 Michael Hanselmann
  rows = raw_data.splitlines()
712 3865ca48 Michael Hanselmann
  return rows[-lines:]
713 3865ca48 Michael Hanselmann
714 3865ca48 Michael Hanselmann
715 3865ca48 Michael Hanselmann
def BytesToMebibyte(value):
716 3865ca48 Michael Hanselmann
  """Converts bytes to mebibytes.
717 3865ca48 Michael Hanselmann

718 3865ca48 Michael Hanselmann
  @type value: int
719 3865ca48 Michael Hanselmann
  @param value: Value in bytes
720 3865ca48 Michael Hanselmann
  @rtype: int
721 3865ca48 Michael Hanselmann
  @return: Value in mebibytes
722 3865ca48 Michael Hanselmann

723 3865ca48 Michael Hanselmann
  """
724 3865ca48 Michael Hanselmann
  return int(round(value / (1024.0 * 1024.0), 0))
725 3865ca48 Michael Hanselmann
726 3865ca48 Michael Hanselmann
727 3865ca48 Michael Hanselmann
def CalculateDirectorySize(path):
728 3865ca48 Michael Hanselmann
  """Calculates the size of a directory recursively.
729 3865ca48 Michael Hanselmann

730 3865ca48 Michael Hanselmann
  @type path: string
731 3865ca48 Michael Hanselmann
  @param path: Path to directory
732 3865ca48 Michael Hanselmann
  @rtype: int
733 3865ca48 Michael Hanselmann
  @return: Size in mebibytes
734 3865ca48 Michael Hanselmann

735 3865ca48 Michael Hanselmann
  """
736 3865ca48 Michael Hanselmann
  size = 0
737 3865ca48 Michael Hanselmann
738 3865ca48 Michael Hanselmann
  for (curpath, _, files) in os.walk(path):
739 3865ca48 Michael Hanselmann
    for filename in files:
740 3865ca48 Michael Hanselmann
      st = os.lstat(PathJoin(curpath, filename))
741 3865ca48 Michael Hanselmann
      size += st.st_size
742 3865ca48 Michael Hanselmann
743 3865ca48 Michael Hanselmann
  return BytesToMebibyte(size)
744 3865ca48 Michael Hanselmann
745 3865ca48 Michael Hanselmann
746 3865ca48 Michael Hanselmann
def GetFilesystemStats(path):
747 3865ca48 Michael Hanselmann
  """Returns the total and free space on a filesystem.
748 3865ca48 Michael Hanselmann

749 3865ca48 Michael Hanselmann
  @type path: string
750 3865ca48 Michael Hanselmann
  @param path: Path on filesystem to be examined
751 3865ca48 Michael Hanselmann
  @rtype: int
752 3865ca48 Michael Hanselmann
  @return: tuple of (Total space, Free space) in mebibytes
753 3865ca48 Michael Hanselmann

754 3865ca48 Michael Hanselmann
  """
755 3865ca48 Michael Hanselmann
  st = os.statvfs(path)
756 3865ca48 Michael Hanselmann
757 3865ca48 Michael Hanselmann
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
758 3865ca48 Michael Hanselmann
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
759 3865ca48 Michael Hanselmann
  return (tsize, fsize)
760 3865ca48 Michael Hanselmann
761 3865ca48 Michael Hanselmann
762 3865ca48 Michael Hanselmann
def ReadPidFile(pidfile):
763 3865ca48 Michael Hanselmann
  """Read a pid from a file.
764 3865ca48 Michael Hanselmann

765 3865ca48 Michael Hanselmann
  @type  pidfile: string
766 3865ca48 Michael Hanselmann
  @param pidfile: path to the file containing the pid
767 3865ca48 Michael Hanselmann
  @rtype: int
768 3865ca48 Michael Hanselmann
  @return: The process id, if the file exists and contains a valid PID,
769 3865ca48 Michael Hanselmann
           otherwise 0
770 3865ca48 Michael Hanselmann

771 3865ca48 Michael Hanselmann
  """
772 3865ca48 Michael Hanselmann
  try:
773 3865ca48 Michael Hanselmann
    raw_data = ReadOneLineFile(pidfile)
774 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
775 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
776 3865ca48 Michael Hanselmann
      logging.exception("Can't read pid file")
777 3865ca48 Michael Hanselmann
    return 0
778 3865ca48 Michael Hanselmann
779 b6522276 Michael Hanselmann
  return _ParsePidFileContents(raw_data)
780 b6522276 Michael Hanselmann
781 b6522276 Michael Hanselmann
782 b6522276 Michael Hanselmann
def _ParsePidFileContents(data):
783 b6522276 Michael Hanselmann
  """Tries to extract a process ID from a PID file's content.
784 b6522276 Michael Hanselmann

785 b6522276 Michael Hanselmann
  @type data: string
786 b6522276 Michael Hanselmann
  @rtype: int
787 b6522276 Michael Hanselmann
  @return: Zero if nothing could be read, PID otherwise
788 b6522276 Michael Hanselmann

789 b6522276 Michael Hanselmann
  """
790 3865ca48 Michael Hanselmann
  try:
791 b6522276 Michael Hanselmann
    pid = int(data)
792 b6522276 Michael Hanselmann
  except (TypeError, ValueError):
793 3865ca48 Michael Hanselmann
    logging.info("Can't parse pid file contents", exc_info=True)
794 3865ca48 Michael Hanselmann
    return 0
795 b6522276 Michael Hanselmann
  else:
796 b6522276 Michael Hanselmann
    return pid
797 3865ca48 Michael Hanselmann
798 3865ca48 Michael Hanselmann
799 3865ca48 Michael Hanselmann
def ReadLockedPidFile(path):
800 3865ca48 Michael Hanselmann
  """Reads a locked PID file.
801 3865ca48 Michael Hanselmann

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

804 3865ca48 Michael Hanselmann
  @type path: string
805 3865ca48 Michael Hanselmann
  @param path: Path to PID file
806 3865ca48 Michael Hanselmann
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
807 3865ca48 Michael Hanselmann

808 3865ca48 Michael Hanselmann
  """
809 3865ca48 Michael Hanselmann
  try:
810 3865ca48 Michael Hanselmann
    fd = os.open(path, os.O_RDONLY)
811 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
812 3865ca48 Michael Hanselmann
    if err.errno == errno.ENOENT:
813 3865ca48 Michael Hanselmann
      # PID file doesn't exist
814 3865ca48 Michael Hanselmann
      return None
815 3865ca48 Michael Hanselmann
    raise
816 3865ca48 Michael Hanselmann
817 3865ca48 Michael Hanselmann
  try:
818 3865ca48 Michael Hanselmann
    try:
819 3865ca48 Michael Hanselmann
      # Try to acquire lock
820 3865ca48 Michael Hanselmann
      filelock.LockFile(fd)
821 3865ca48 Michael Hanselmann
    except errors.LockError:
822 3865ca48 Michael Hanselmann
      # Couldn't lock, daemon is running
823 3865ca48 Michael Hanselmann
      return int(os.read(fd, 100))
824 3865ca48 Michael Hanselmann
  finally:
825 3865ca48 Michael Hanselmann
    os.close(fd)
826 3865ca48 Michael Hanselmann
827 3865ca48 Michael Hanselmann
  return None
828 3865ca48 Michael Hanselmann
829 3865ca48 Michael Hanselmann
830 3865ca48 Michael Hanselmann
def AddAuthorizedKey(file_obj, key):
831 3865ca48 Michael Hanselmann
  """Adds an SSH public key to an authorized_keys file.
832 3865ca48 Michael Hanselmann

833 3865ca48 Michael Hanselmann
  @type file_obj: str or file handle
834 3865ca48 Michael Hanselmann
  @param file_obj: path to authorized_keys file
835 3865ca48 Michael Hanselmann
  @type key: str
836 3865ca48 Michael Hanselmann
  @param key: string containing key
837 3865ca48 Michael Hanselmann

838 3865ca48 Michael Hanselmann
  """
839 3865ca48 Michael Hanselmann
  key_fields = key.split()
840 3865ca48 Michael Hanselmann
841 3865ca48 Michael Hanselmann
  if isinstance(file_obj, basestring):
842 d0c8c01d Iustin Pop
    f = open(file_obj, "a+")
843 3865ca48 Michael Hanselmann
  else:
844 3865ca48 Michael Hanselmann
    f = file_obj
845 3865ca48 Michael Hanselmann
846 3865ca48 Michael Hanselmann
  try:
847 3865ca48 Michael Hanselmann
    nl = True
848 3865ca48 Michael Hanselmann
    for line in f:
849 3865ca48 Michael Hanselmann
      # Ignore whitespace changes
850 3865ca48 Michael Hanselmann
      if line.split() == key_fields:
851 3865ca48 Michael Hanselmann
        break
852 d0c8c01d Iustin Pop
      nl = line.endswith("\n")
853 3865ca48 Michael Hanselmann
    else:
854 3865ca48 Michael Hanselmann
      if not nl:
855 3865ca48 Michael Hanselmann
        f.write("\n")
856 d0c8c01d Iustin Pop
      f.write(key.rstrip("\r\n"))
857 3865ca48 Michael Hanselmann
      f.write("\n")
858 3865ca48 Michael Hanselmann
      f.flush()
859 3865ca48 Michael Hanselmann
  finally:
860 3865ca48 Michael Hanselmann
    f.close()
861 3865ca48 Michael Hanselmann
862 3865ca48 Michael Hanselmann
863 3865ca48 Michael Hanselmann
def RemoveAuthorizedKey(file_name, key):
864 3865ca48 Michael Hanselmann
  """Removes an SSH public key from an authorized_keys file.
865 3865ca48 Michael Hanselmann

866 3865ca48 Michael Hanselmann
  @type file_name: str
867 3865ca48 Michael Hanselmann
  @param file_name: path to authorized_keys file
868 3865ca48 Michael Hanselmann
  @type key: str
869 3865ca48 Michael Hanselmann
  @param key: string containing key
870 3865ca48 Michael Hanselmann

871 3865ca48 Michael Hanselmann
  """
872 3865ca48 Michael Hanselmann
  key_fields = key.split()
873 3865ca48 Michael Hanselmann
874 3865ca48 Michael Hanselmann
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
875 3865ca48 Michael Hanselmann
  try:
876 d0c8c01d Iustin Pop
    out = os.fdopen(fd, "w")
877 3865ca48 Michael Hanselmann
    try:
878 d0c8c01d Iustin Pop
      f = open(file_name, "r")
879 3865ca48 Michael Hanselmann
      try:
880 3865ca48 Michael Hanselmann
        for line in f:
881 3865ca48 Michael Hanselmann
          # Ignore whitespace changes while comparing lines
882 3865ca48 Michael Hanselmann
          if line.split() != key_fields:
883 3865ca48 Michael Hanselmann
            out.write(line)
884 3865ca48 Michael Hanselmann
885 3865ca48 Michael Hanselmann
        out.flush()
886 3865ca48 Michael Hanselmann
        os.rename(tmpname, file_name)
887 3865ca48 Michael Hanselmann
      finally:
888 3865ca48 Michael Hanselmann
        f.close()
889 3865ca48 Michael Hanselmann
    finally:
890 3865ca48 Michael Hanselmann
      out.close()
891 3865ca48 Michael Hanselmann
  except:
892 3865ca48 Michael Hanselmann
    RemoveFile(tmpname)
893 3865ca48 Michael Hanselmann
    raise
894 3865ca48 Michael Hanselmann
895 3865ca48 Michael Hanselmann
896 3865ca48 Michael Hanselmann
def DaemonPidFileName(name):
897 3865ca48 Michael Hanselmann
  """Compute a ganeti pid file absolute path
898 3865ca48 Michael Hanselmann

899 3865ca48 Michael Hanselmann
  @type name: str
900 3865ca48 Michael Hanselmann
  @param name: the daemon name
901 3865ca48 Michael Hanselmann
  @rtype: str
902 3865ca48 Michael Hanselmann
  @return: the full path to the pidfile corresponding to the given
903 3865ca48 Michael Hanselmann
      daemon name
904 3865ca48 Michael Hanselmann

905 3865ca48 Michael Hanselmann
  """
906 3865ca48 Michael Hanselmann
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
907 3865ca48 Michael Hanselmann
908 3865ca48 Michael Hanselmann
909 3865ca48 Michael Hanselmann
def WritePidFile(pidfile):
910 3865ca48 Michael Hanselmann
  """Write the current process pidfile.
911 3865ca48 Michael Hanselmann

912 3865ca48 Michael Hanselmann
  @type pidfile: string
913 3865ca48 Michael Hanselmann
  @param pidfile: the path to the file to be written
914 3865ca48 Michael Hanselmann
  @raise errors.LockError: if the pid file already exists and
915 3865ca48 Michael Hanselmann
      points to a live process
916 3865ca48 Michael Hanselmann
  @rtype: int
917 3865ca48 Michael Hanselmann
  @return: the file descriptor of the lock file; do not close this unless
918 3865ca48 Michael Hanselmann
      you want to unlock the pid file
919 3865ca48 Michael Hanselmann

920 3865ca48 Michael Hanselmann
  """
921 3865ca48 Michael Hanselmann
  # We don't rename nor truncate the file to not drop locks under
922 3865ca48 Michael Hanselmann
  # existing processes
923 b6522276 Michael Hanselmann
  fd_pidfile = os.open(pidfile, os.O_RDWR | os.O_CREAT, 0600)
924 3865ca48 Michael Hanselmann
925 3865ca48 Michael Hanselmann
  # Lock the PID file (and fail if not possible to do so). Any code
926 3865ca48 Michael Hanselmann
  # wanting to send a signal to the daemon should try to lock the PID
927 3865ca48 Michael Hanselmann
  # file before reading it. If acquiring the lock succeeds, the daemon is
928 3865ca48 Michael Hanselmann
  # no longer running and the signal should not be sent.
929 b6522276 Michael Hanselmann
  try:
930 b6522276 Michael Hanselmann
    filelock.LockFile(fd_pidfile)
931 b6522276 Michael Hanselmann
  except errors.LockError:
932 b6522276 Michael Hanselmann
    msg = ["PID file '%s' is already locked by another process" % pidfile]
933 b6522276 Michael Hanselmann
    # Try to read PID file
934 b6522276 Michael Hanselmann
    pid = _ParsePidFileContents(os.read(fd_pidfile, 100))
935 b6522276 Michael Hanselmann
    if pid > 0:
936 b6522276 Michael Hanselmann
      msg.append(", PID read from file is %s" % pid)
937 b6522276 Michael Hanselmann
    raise errors.PidFileLockError("".join(msg))
938 3865ca48 Michael Hanselmann
939 3865ca48 Michael Hanselmann
  os.write(fd_pidfile, "%d\n" % os.getpid())
940 3865ca48 Michael Hanselmann
941 3865ca48 Michael Hanselmann
  return fd_pidfile
942 3865ca48 Michael Hanselmann
943 3865ca48 Michael Hanselmann
944 3865ca48 Michael Hanselmann
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
945 3865ca48 Michael Hanselmann
  """Reads the watcher pause file.
946 3865ca48 Michael Hanselmann

947 3865ca48 Michael Hanselmann
  @type filename: string
948 3865ca48 Michael Hanselmann
  @param filename: Path to watcher pause file
949 3865ca48 Michael Hanselmann
  @type now: None, float or int
950 3865ca48 Michael Hanselmann
  @param now: Current time as Unix timestamp
951 3865ca48 Michael Hanselmann
  @type remove_after: int
952 3865ca48 Michael Hanselmann
  @param remove_after: Remove watcher pause file after specified amount of
953 3865ca48 Michael Hanselmann
    seconds past the pause end time
954 3865ca48 Michael Hanselmann

955 3865ca48 Michael Hanselmann
  """
956 3865ca48 Michael Hanselmann
  if now is None:
957 3865ca48 Michael Hanselmann
    now = time.time()
958 3865ca48 Michael Hanselmann
959 3865ca48 Michael Hanselmann
  try:
960 3865ca48 Michael Hanselmann
    value = ReadFile(filename)
961 3865ca48 Michael Hanselmann
  except IOError, err:
962 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
963 3865ca48 Michael Hanselmann
      raise
964 3865ca48 Michael Hanselmann
    value = None
965 3865ca48 Michael Hanselmann
966 3865ca48 Michael Hanselmann
  if value is not None:
967 3865ca48 Michael Hanselmann
    try:
968 3865ca48 Michael Hanselmann
      value = int(value)
969 3865ca48 Michael Hanselmann
    except ValueError:
970 3865ca48 Michael Hanselmann
      logging.warning(("Watcher pause file (%s) contains invalid value,"
971 3865ca48 Michael Hanselmann
                       " removing it"), filename)
972 3865ca48 Michael Hanselmann
      RemoveFile(filename)
973 3865ca48 Michael Hanselmann
      value = None
974 3865ca48 Michael Hanselmann
975 3865ca48 Michael Hanselmann
    if value is not None:
976 3865ca48 Michael Hanselmann
      # Remove file if it's outdated
977 3865ca48 Michael Hanselmann
      if now > (value + remove_after):
978 3865ca48 Michael Hanselmann
        RemoveFile(filename)
979 3865ca48 Michael Hanselmann
        value = None
980 3865ca48 Michael Hanselmann
981 3865ca48 Michael Hanselmann
      elif now > value:
982 3865ca48 Michael Hanselmann
        value = None
983 3865ca48 Michael Hanselmann
984 3865ca48 Michael Hanselmann
  return value
985 90e234a6 Michael Hanselmann
986 90e234a6 Michael Hanselmann
987 90e234a6 Michael Hanselmann
def NewUUID():
988 90e234a6 Michael Hanselmann
  """Returns a random UUID.
989 90e234a6 Michael Hanselmann

990 90e234a6 Michael Hanselmann
  @note: This is a Linux-specific method as it uses the /proc
991 90e234a6 Michael Hanselmann
      filesystem.
992 90e234a6 Michael Hanselmann
  @rtype: str
993 90e234a6 Michael Hanselmann

994 90e234a6 Michael Hanselmann
  """
995 90e234a6 Michael Hanselmann
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
996 0c1a5b1e Agata Murawska
997 0c1a5b1e Agata Murawska
998 0c1a5b1e Agata Murawska
class TemporaryFileManager(object):
999 0c1a5b1e Agata Murawska
  """Stores the list of files to be deleted and removes them on demand.
1000 0c1a5b1e Agata Murawska

1001 0c1a5b1e Agata Murawska
  """
1002 0c1a5b1e Agata Murawska
1003 0c1a5b1e Agata Murawska
  def __init__(self):
1004 0c1a5b1e Agata Murawska
    self._files = []
1005 0c1a5b1e Agata Murawska
1006 0c1a5b1e Agata Murawska
  def __del__(self):
1007 0c1a5b1e Agata Murawska
    self.Cleanup()
1008 0c1a5b1e Agata Murawska
1009 0c1a5b1e Agata Murawska
  def Add(self, filename):
1010 0c1a5b1e Agata Murawska
    """Add file to list of files to be deleted.
1011 0c1a5b1e Agata Murawska

1012 0c1a5b1e Agata Murawska
    @type filename: string
1013 0c1a5b1e Agata Murawska
    @param filename: path to filename to be added
1014 0c1a5b1e Agata Murawska

1015 0c1a5b1e Agata Murawska
    """
1016 0c1a5b1e Agata Murawska
    self._files.append(filename)
1017 0c1a5b1e Agata Murawska
1018 0c1a5b1e Agata Murawska
  def Remove(self, filename):
1019 0c1a5b1e Agata Murawska
    """Remove file from list of files to be deleted.
1020 0c1a5b1e Agata Murawska

1021 0c1a5b1e Agata Murawska
    @type filename: string
1022 0c1a5b1e Agata Murawska
    @param filename: path to filename to be deleted
1023 0c1a5b1e Agata Murawska

1024 0c1a5b1e Agata Murawska
    """
1025 0c1a5b1e Agata Murawska
    self._files.remove(filename)
1026 0c1a5b1e Agata Murawska
1027 0c1a5b1e Agata Murawska
  def Cleanup(self):
1028 0c1a5b1e Agata Murawska
    """Delete all files marked for deletion
1029 0c1a5b1e Agata Murawska

1030 0c1a5b1e Agata Murawska
    """
1031 0c1a5b1e Agata Murawska
    while self._files:
1032 0c1a5b1e Agata Murawska
      RemoveFile(self._files.pop())