Statistics
| Branch: | Tag: | Revision:

root / lib / utils / io.py @ c47eddb8

History | View | Annotate | Download (26.8 kB)

1 3865ca48 Michael Hanselmann
#
2 3865ca48 Michael Hanselmann
#
3 3865ca48 Michael Hanselmann
4 3865ca48 Michael Hanselmann
# Copyright (C) 2006, 2007, 2010, 2011 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 c47eddb8 Bernardo Dal Seno
# Possible values for keep_perms in WriteFile()
42 c47eddb8 Bernardo Dal Seno
KP_NEVER = 0
43 c47eddb8 Bernardo Dal Seno
KP_ALWAYS = 1
44 c47eddb8 Bernardo Dal Seno
KP_IF_EXISTS = 2
45 c47eddb8 Bernardo Dal Seno
46 c47eddb8 Bernardo Dal Seno
KEEP_PERMS_VALUES = [
47 c47eddb8 Bernardo Dal Seno
  KP_NEVER,
48 c47eddb8 Bernardo Dal Seno
  KP_ALWAYS,
49 c47eddb8 Bernardo Dal Seno
  KP_IF_EXISTS,
50 c47eddb8 Bernardo Dal Seno
  ]
51 c47eddb8 Bernardo Dal Seno
52 90e234a6 Michael Hanselmann
53 eb93b673 Guido Trotter
def ErrnoOrStr(err):
54 eb93b673 Guido Trotter
  """Format an EnvironmentError exception.
55 eb93b673 Guido Trotter

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

60 eb93b673 Guido Trotter
  @type err: L{EnvironmentError}
61 eb93b673 Guido Trotter
  @param err: the exception to format
62 eb93b673 Guido Trotter

63 eb93b673 Guido Trotter
  """
64 eb93b673 Guido Trotter
  if hasattr(err, "errno"):
65 eb93b673 Guido Trotter
    detail = errno.errorcode[err.errno]
66 eb93b673 Guido Trotter
  else:
67 eb93b673 Guido Trotter
    detail = str(err)
68 eb93b673 Guido Trotter
  return detail
69 eb93b673 Guido Trotter
70 eb93b673 Guido Trotter
71 0e5084ee Michael Hanselmann
def ReadFile(file_name, size=-1, preread=None):
72 3865ca48 Michael Hanselmann
  """Reads a file.
73 3865ca48 Michael Hanselmann

74 3865ca48 Michael Hanselmann
  @type size: int
75 3865ca48 Michael Hanselmann
  @param size: Read at most size bytes (if negative, entire file)
76 0e5084ee Michael Hanselmann
  @type preread: callable receiving file handle as single parameter
77 0e5084ee Michael Hanselmann
  @param preread: Function called before file is read
78 3865ca48 Michael Hanselmann
  @rtype: str
79 3865ca48 Michael Hanselmann
  @return: the (possibly partial) content of the file
80 3865ca48 Michael Hanselmann

81 3865ca48 Michael Hanselmann
  """
82 3865ca48 Michael Hanselmann
  f = open(file_name, "r")
83 3865ca48 Michael Hanselmann
  try:
84 0e5084ee Michael Hanselmann
    if preread:
85 0e5084ee Michael Hanselmann
      preread(f)
86 0e5084ee Michael Hanselmann
87 3865ca48 Michael Hanselmann
    return f.read(size)
88 3865ca48 Michael Hanselmann
  finally:
89 3865ca48 Michael Hanselmann
    f.close()
90 3865ca48 Michael Hanselmann
91 3865ca48 Michael Hanselmann
92 3865ca48 Michael Hanselmann
def WriteFile(file_name, fn=None, data=None,
93 3865ca48 Michael Hanselmann
              mode=None, uid=-1, gid=-1,
94 3865ca48 Michael Hanselmann
              atime=None, mtime=None, close=True,
95 3865ca48 Michael Hanselmann
              dry_run=False, backup=False,
96 c47eddb8 Bernardo Dal Seno
              prewrite=None, postwrite=None, keep_perms=KP_NEVER):
97 3865ca48 Michael Hanselmann
  """(Over)write a file atomically.
98 3865ca48 Michael Hanselmann

99 3865ca48 Michael Hanselmann
  The file_name and either fn (a function taking one argument, the
100 3865ca48 Michael Hanselmann
  file descriptor, and which should write the data to it) or data (the
101 3865ca48 Michael Hanselmann
  contents of the file) must be passed. The other arguments are
102 3865ca48 Michael Hanselmann
  optional and allow setting the file mode, owner and group, and the
103 3865ca48 Michael Hanselmann
  mtime/atime of the file.
104 3865ca48 Michael Hanselmann

105 3865ca48 Michael Hanselmann
  If the function doesn't raise an exception, it has succeeded and the
106 3865ca48 Michael Hanselmann
  target file has the new contents. If the function has raised an
107 3865ca48 Michael Hanselmann
  exception, an existing target file should be unmodified and the
108 3865ca48 Michael Hanselmann
  temporary file should be removed.
109 3865ca48 Michael Hanselmann

110 3865ca48 Michael Hanselmann
  @type file_name: str
111 3865ca48 Michael Hanselmann
  @param file_name: the target filename
112 3865ca48 Michael Hanselmann
  @type fn: callable
113 3865ca48 Michael Hanselmann
  @param fn: content writing function, called with
114 3865ca48 Michael Hanselmann
      file descriptor as parameter
115 3865ca48 Michael Hanselmann
  @type data: str
116 3865ca48 Michael Hanselmann
  @param data: contents of the file
117 3865ca48 Michael Hanselmann
  @type mode: int
118 3865ca48 Michael Hanselmann
  @param mode: file mode
119 3865ca48 Michael Hanselmann
  @type uid: int
120 3865ca48 Michael Hanselmann
  @param uid: the owner of the file
121 3865ca48 Michael Hanselmann
  @type gid: int
122 3865ca48 Michael Hanselmann
  @param gid: the group of the file
123 3865ca48 Michael Hanselmann
  @type atime: int
124 3865ca48 Michael Hanselmann
  @param atime: a custom access time to be set on the file
125 3865ca48 Michael Hanselmann
  @type mtime: int
126 3865ca48 Michael Hanselmann
  @param mtime: a custom modification time to be set on the file
127 3865ca48 Michael Hanselmann
  @type close: boolean
128 3865ca48 Michael Hanselmann
  @param close: whether to close file after writing it
129 3865ca48 Michael Hanselmann
  @type prewrite: callable
130 3865ca48 Michael Hanselmann
  @param prewrite: function to be called before writing content
131 3865ca48 Michael Hanselmann
  @type postwrite: callable
132 3865ca48 Michael Hanselmann
  @param postwrite: function to be called after writing content
133 c47eddb8 Bernardo Dal Seno
  @type keep_perms: members of L{KEEP_PERMS_VALUES}
134 c47eddb8 Bernardo Dal Seno
  @param keep_perms: if L{KP_NEVER} (default), owner, group, and mode are
135 c47eddb8 Bernardo Dal Seno
      taken from the other parameters; if L{KP_ALWAYS}, owner, group, and
136 c47eddb8 Bernardo Dal Seno
      mode are copied from the existing file; if L{KP_IF_EXISTS}, owner,
137 c47eddb8 Bernardo Dal Seno
      group, and mode are taken from the file, and if the file doesn't
138 c47eddb8 Bernardo Dal Seno
      exist, they are taken from the other parameters. It is an error to
139 c47eddb8 Bernardo Dal Seno
      pass L{KP_ALWAYS} when the file doesn't exist or when C{uid}, C{gid},
140 c47eddb8 Bernardo Dal Seno
      or C{mode} are set to non-default values.
141 3865ca48 Michael Hanselmann

142 3865ca48 Michael Hanselmann
  @rtype: None or int
143 3865ca48 Michael Hanselmann
  @return: None if the 'close' parameter evaluates to True,
144 3865ca48 Michael Hanselmann
      otherwise the file descriptor
145 3865ca48 Michael Hanselmann

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

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

240 3865ca48 Michael Hanselmann
  Either the path to the file or the fd must be given.
241 3865ca48 Michael Hanselmann

242 3865ca48 Michael Hanselmann
  @param path: the file path
243 3865ca48 Michael Hanselmann
  @param fd: a file descriptor
244 3865ca48 Michael Hanselmann
  @return: a tuple of (device number, inode number, mtime)
245 3865ca48 Michael Hanselmann

246 3865ca48 Michael Hanselmann
  """
247 3865ca48 Michael Hanselmann
  if [path, fd].count(None) != 1:
248 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("One and only one of fd/path must be given")
249 3865ca48 Michael Hanselmann
250 3865ca48 Michael Hanselmann
  if fd is None:
251 3865ca48 Michael Hanselmann
    st = os.stat(path)
252 3865ca48 Michael Hanselmann
  else:
253 3865ca48 Michael Hanselmann
    st = os.fstat(fd)
254 3865ca48 Michael Hanselmann
255 3865ca48 Michael Hanselmann
  return (st.st_dev, st.st_ino, st.st_mtime)
256 3865ca48 Michael Hanselmann
257 3865ca48 Michael Hanselmann
258 3865ca48 Michael Hanselmann
def VerifyFileID(fi_disk, fi_ours):
259 3865ca48 Michael Hanselmann
  """Verifies that two file IDs are matching.
260 3865ca48 Michael Hanselmann

261 3865ca48 Michael Hanselmann
  Differences in the inode/device are not accepted, but and older
262 3865ca48 Michael Hanselmann
  timestamp for fi_disk is accepted.
263 3865ca48 Michael Hanselmann

264 3865ca48 Michael Hanselmann
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
265 3865ca48 Michael Hanselmann
      file data
266 3865ca48 Michael Hanselmann
  @param fi_ours: tuple (dev, inode, mtime) representing the last
267 3865ca48 Michael Hanselmann
      written file data
268 3865ca48 Michael Hanselmann
  @rtype: boolean
269 3865ca48 Michael Hanselmann

270 3865ca48 Michael Hanselmann
  """
271 3865ca48 Michael Hanselmann
  (d1, i1, m1) = fi_disk
272 3865ca48 Michael Hanselmann
  (d2, i2, m2) = fi_ours
273 3865ca48 Michael Hanselmann
274 3865ca48 Michael Hanselmann
  return (d1, i1) == (d2, i2) and m1 <= m2
275 3865ca48 Michael Hanselmann
276 3865ca48 Michael Hanselmann
277 3865ca48 Michael Hanselmann
def SafeWriteFile(file_name, file_id, **kwargs):
278 3865ca48 Michael Hanselmann
  """Wraper over L{WriteFile} that locks the target file.
279 3865ca48 Michael Hanselmann

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

283 3865ca48 Michael Hanselmann
  @type file_name: str
284 3865ca48 Michael Hanselmann
  @param file_name: the target filename
285 3865ca48 Michael Hanselmann
  @type file_id: tuple
286 3865ca48 Michael Hanselmann
  @param file_id: a result from L{GetFileID}
287 3865ca48 Michael Hanselmann

288 3865ca48 Michael Hanselmann
  """
289 3865ca48 Michael Hanselmann
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
290 3865ca48 Michael Hanselmann
  try:
291 3865ca48 Michael Hanselmann
    filelock.LockFile(fd)
292 3865ca48 Michael Hanselmann
    if file_id is not None:
293 3865ca48 Michael Hanselmann
      disk_id = GetFileID(fd=fd)
294 3865ca48 Michael Hanselmann
      if not VerifyFileID(disk_id, file_id):
295 3865ca48 Michael Hanselmann
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
296 3865ca48 Michael Hanselmann
                               " since last written" % file_name)
297 3865ca48 Michael Hanselmann
    return WriteFile(file_name, **kwargs)
298 3865ca48 Michael Hanselmann
  finally:
299 3865ca48 Michael Hanselmann
    os.close(fd)
300 3865ca48 Michael Hanselmann
301 3865ca48 Michael Hanselmann
302 3865ca48 Michael Hanselmann
def ReadOneLineFile(file_name, strict=False):
303 3865ca48 Michael Hanselmann
  """Return the first non-empty line from a file.
304 3865ca48 Michael Hanselmann

305 3865ca48 Michael Hanselmann
  @type strict: boolean
306 3865ca48 Michael Hanselmann
  @param strict: if True, abort if the file has more than one
307 3865ca48 Michael Hanselmann
      non-empty line
308 3865ca48 Michael Hanselmann

309 3865ca48 Michael Hanselmann
  """
310 3865ca48 Michael Hanselmann
  file_lines = ReadFile(file_name).splitlines()
311 3865ca48 Michael Hanselmann
  full_lines = filter(bool, file_lines)
312 3865ca48 Michael Hanselmann
  if not file_lines or not full_lines:
313 3865ca48 Michael Hanselmann
    raise errors.GenericError("No data in one-liner file %s" % file_name)
314 3865ca48 Michael Hanselmann
  elif strict and len(full_lines) > 1:
315 3865ca48 Michael Hanselmann
    raise errors.GenericError("Too many lines in one-liner file %s" %
316 3865ca48 Michael Hanselmann
                              file_name)
317 3865ca48 Michael Hanselmann
  return full_lines[0]
318 3865ca48 Michael Hanselmann
319 3865ca48 Michael Hanselmann
320 3865ca48 Michael Hanselmann
def RemoveFile(filename):
321 3865ca48 Michael Hanselmann
  """Remove a file ignoring some errors.
322 3865ca48 Michael Hanselmann

323 3865ca48 Michael Hanselmann
  Remove a file, ignoring non-existing ones or directories. Other
324 3865ca48 Michael Hanselmann
  errors are passed.
325 3865ca48 Michael Hanselmann

326 3865ca48 Michael Hanselmann
  @type filename: str
327 3865ca48 Michael Hanselmann
  @param filename: the file to be removed
328 3865ca48 Michael Hanselmann

329 3865ca48 Michael Hanselmann
  """
330 3865ca48 Michael Hanselmann
  try:
331 3865ca48 Michael Hanselmann
    os.unlink(filename)
332 3865ca48 Michael Hanselmann
  except OSError, err:
333 3865ca48 Michael Hanselmann
    if err.errno not in (errno.ENOENT, errno.EISDIR):
334 3865ca48 Michael Hanselmann
      raise
335 3865ca48 Michael Hanselmann
336 3865ca48 Michael Hanselmann
337 3865ca48 Michael Hanselmann
def RemoveDir(dirname):
338 3865ca48 Michael Hanselmann
  """Remove an empty directory.
339 3865ca48 Michael Hanselmann

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

344 3865ca48 Michael Hanselmann
  @type dirname: str
345 3865ca48 Michael Hanselmann
  @param dirname: the empty directory to be removed
346 3865ca48 Michael Hanselmann

347 3865ca48 Michael Hanselmann
  """
348 3865ca48 Michael Hanselmann
  try:
349 3865ca48 Michael Hanselmann
    os.rmdir(dirname)
350 3865ca48 Michael Hanselmann
  except OSError, err:
351 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
352 3865ca48 Michael Hanselmann
      raise
353 3865ca48 Michael Hanselmann
354 3865ca48 Michael Hanselmann
355 8e5a705d René Nussbaumer
def RenameFile(old, new, mkdir=False, mkdir_mode=0750, dir_uid=None,
356 8e5a705d René Nussbaumer
               dir_gid=None):
357 3865ca48 Michael Hanselmann
  """Renames a file.
358 3865ca48 Michael Hanselmann

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

362 3865ca48 Michael Hanselmann
  @type old: string
363 3865ca48 Michael Hanselmann
  @param old: Original path
364 3865ca48 Michael Hanselmann
  @type new: string
365 3865ca48 Michael Hanselmann
  @param new: New path
366 3865ca48 Michael Hanselmann
  @type mkdir: bool
367 3865ca48 Michael Hanselmann
  @param mkdir: Whether to create target directory if it doesn't exist
368 3865ca48 Michael Hanselmann
  @type mkdir_mode: int
369 3865ca48 Michael Hanselmann
  @param mkdir_mode: Mode for newly created directories
370 8e5a705d René Nussbaumer
  @type dir_uid: int
371 8e5a705d René Nussbaumer
  @param dir_uid: The uid for the (if fresh created) dir
372 8e5a705d René Nussbaumer
  @type dir_gid: int
373 8e5a705d René Nussbaumer
  @param dir_gid: The gid for the (if fresh created) dir
374 3865ca48 Michael Hanselmann

375 3865ca48 Michael Hanselmann
  """
376 3865ca48 Michael Hanselmann
  try:
377 3865ca48 Michael Hanselmann
    return os.rename(old, new)
378 3865ca48 Michael Hanselmann
  except OSError, err:
379 3865ca48 Michael Hanselmann
    # In at least one use case of this function, the job queue, directory
380 3865ca48 Michael Hanselmann
    # creation is very rare. Checking for the directory before renaming is not
381 3865ca48 Michael Hanselmann
    # as efficient.
382 3865ca48 Michael Hanselmann
    if mkdir and err.errno == errno.ENOENT:
383 3865ca48 Michael Hanselmann
      # Create directory and try again
384 8e5a705d René Nussbaumer
      dir_path = os.path.dirname(new)
385 9c2b3a70 René Nussbaumer
      MakeDirWithPerm(dir_path, mkdir_mode, dir_uid, dir_gid)
386 3865ca48 Michael Hanselmann
387 3865ca48 Michael Hanselmann
      return os.rename(old, new)
388 3865ca48 Michael Hanselmann
389 3865ca48 Michael Hanselmann
    raise
390 3865ca48 Michael Hanselmann
391 3865ca48 Michael Hanselmann
392 9c2b3a70 René Nussbaumer
def EnforcePermission(path, mode, uid=None, gid=None, must_exist=True,
393 b81b3c96 René Nussbaumer
                      _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
394 b81b3c96 René Nussbaumer
  """Enforces that given path has given permissions.
395 b81b3c96 René Nussbaumer

396 b81b3c96 René Nussbaumer
  @param path: The path to the file
397 b81b3c96 René Nussbaumer
  @param mode: The mode of the file
398 b81b3c96 René Nussbaumer
  @param uid: The uid of the owner of this file
399 b81b3c96 René Nussbaumer
  @param gid: The gid of the owner of this file
400 b81b3c96 René Nussbaumer
  @param must_exist: Specifies if non-existance of path will be an error
401 b81b3c96 René Nussbaumer
  @param _chmod_fn: chmod function to use (unittest only)
402 b81b3c96 René Nussbaumer
  @param _chown_fn: chown function to use (unittest only)
403 b81b3c96 René Nussbaumer

404 b81b3c96 René Nussbaumer
  """
405 b81b3c96 René Nussbaumer
  logging.debug("Checking %s", path)
406 9c2b3a70 René Nussbaumer
407 9c2b3a70 René Nussbaumer
  # chown takes -1 if you want to keep one part of the ownership, however
408 9c2b3a70 René Nussbaumer
  # None is Python standard for that. So we remap them here.
409 9c2b3a70 René Nussbaumer
  if uid is None:
410 9c2b3a70 René Nussbaumer
    uid = -1
411 9c2b3a70 René Nussbaumer
  if gid is None:
412 9c2b3a70 René Nussbaumer
    gid = -1
413 9c2b3a70 René Nussbaumer
414 b81b3c96 René Nussbaumer
  try:
415 b81b3c96 René Nussbaumer
    st = _stat_fn(path)
416 b81b3c96 René Nussbaumer
417 b81b3c96 René Nussbaumer
    fmode = stat.S_IMODE(st[stat.ST_MODE])
418 b81b3c96 René Nussbaumer
    if fmode != mode:
419 b81b3c96 René Nussbaumer
      logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
420 b81b3c96 René Nussbaumer
      _chmod_fn(path, mode)
421 b81b3c96 René Nussbaumer
422 b81b3c96 René Nussbaumer
    if max(uid, gid) > -1:
423 b81b3c96 René Nussbaumer
      fuid = st[stat.ST_UID]
424 b81b3c96 René Nussbaumer
      fgid = st[stat.ST_GID]
425 b81b3c96 René Nussbaumer
      if fuid != uid or fgid != gid:
426 b81b3c96 René Nussbaumer
        logging.debug("Changing owner of %s from UID %s/GID %s to"
427 b81b3c96 René Nussbaumer
                      " UID %s/GID %s", path, fuid, fgid, uid, gid)
428 b81b3c96 René Nussbaumer
        _chown_fn(path, uid, gid)
429 b81b3c96 René Nussbaumer
  except EnvironmentError, err:
430 b81b3c96 René Nussbaumer
    if err.errno == errno.ENOENT:
431 b81b3c96 René Nussbaumer
      if must_exist:
432 b81b3c96 René Nussbaumer
        raise errors.GenericError("Path %s should exist, but does not" % path)
433 b81b3c96 René Nussbaumer
    else:
434 b81b3c96 René Nussbaumer
      raise errors.GenericError("Error while changing permissions on %s: %s" %
435 b81b3c96 René Nussbaumer
                                (path, err))
436 b81b3c96 René Nussbaumer
437 b81b3c96 René Nussbaumer
438 b81b3c96 René Nussbaumer
def MakeDirWithPerm(path, mode, uid, gid, _lstat_fn=os.lstat,
439 b81b3c96 René Nussbaumer
                    _mkdir_fn=os.mkdir, _perm_fn=EnforcePermission):
440 b81b3c96 René Nussbaumer
  """Enforces that given path is a dir and has given mode, uid and gid set.
441 b81b3c96 René Nussbaumer

442 b81b3c96 René Nussbaumer
  @param path: The path to the file
443 b81b3c96 René Nussbaumer
  @param mode: The mode of the file
444 b81b3c96 René Nussbaumer
  @param uid: The uid of the owner of this file
445 b81b3c96 René Nussbaumer
  @param gid: The gid of the owner of this file
446 b81b3c96 René Nussbaumer
  @param _lstat_fn: Stat function to use (unittest only)
447 b81b3c96 René Nussbaumer
  @param _mkdir_fn: mkdir function to use (unittest only)
448 b81b3c96 René Nussbaumer
  @param _perm_fn: permission setter function to use (unittest only)
449 b81b3c96 René Nussbaumer

450 b81b3c96 René Nussbaumer
  """
451 b81b3c96 René Nussbaumer
  logging.debug("Checking directory %s", path)
452 b81b3c96 René Nussbaumer
  try:
453 b81b3c96 René Nussbaumer
    # We don't want to follow symlinks
454 b81b3c96 René Nussbaumer
    st = _lstat_fn(path)
455 b81b3c96 René Nussbaumer
  except EnvironmentError, err:
456 b81b3c96 René Nussbaumer
    if err.errno != errno.ENOENT:
457 b81b3c96 René Nussbaumer
      raise errors.GenericError("stat(2) on %s failed: %s" % (path, err))
458 b81b3c96 René Nussbaumer
    _mkdir_fn(path)
459 b81b3c96 René Nussbaumer
  else:
460 b81b3c96 René Nussbaumer
    if not stat.S_ISDIR(st[stat.ST_MODE]):
461 b81b3c96 René Nussbaumer
      raise errors.GenericError(("Path %s is expected to be a directory, but "
462 b81b3c96 René Nussbaumer
                                 "isn't") % path)
463 b81b3c96 René Nussbaumer
464 b81b3c96 René Nussbaumer
  _perm_fn(path, mode, uid=uid, gid=gid)
465 b81b3c96 René Nussbaumer
466 b81b3c96 René Nussbaumer
467 3865ca48 Michael Hanselmann
def Makedirs(path, mode=0750):
468 3865ca48 Michael Hanselmann
  """Super-mkdir; create a leaf directory and all intermediate ones.
469 3865ca48 Michael Hanselmann

470 3865ca48 Michael Hanselmann
  This is a wrapper around C{os.makedirs} adding error handling not implemented
471 3865ca48 Michael Hanselmann
  before Python 2.5.
472 3865ca48 Michael Hanselmann

473 3865ca48 Michael Hanselmann
  """
474 3865ca48 Michael Hanselmann
  try:
475 3865ca48 Michael Hanselmann
    os.makedirs(path, mode)
476 3865ca48 Michael Hanselmann
  except OSError, err:
477 3865ca48 Michael Hanselmann
    # Ignore EEXIST. This is only handled in os.makedirs as included in
478 3865ca48 Michael Hanselmann
    # Python 2.5 and above.
479 3865ca48 Michael Hanselmann
    if err.errno != errno.EEXIST or not os.path.exists(path):
480 3865ca48 Michael Hanselmann
      raise
481 3865ca48 Michael Hanselmann
482 3865ca48 Michael Hanselmann
483 3865ca48 Michael Hanselmann
def TimestampForFilename():
484 3865ca48 Michael Hanselmann
  """Returns the current time formatted for filenames.
485 3865ca48 Michael Hanselmann

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

489 3865ca48 Michael Hanselmann
  """
490 3865ca48 Michael Hanselmann
  return time.strftime("%Y-%m-%d_%H_%M_%S")
491 3865ca48 Michael Hanselmann
492 3865ca48 Michael Hanselmann
493 3865ca48 Michael Hanselmann
def CreateBackup(file_name):
494 3865ca48 Michael Hanselmann
  """Creates a backup of a file.
495 3865ca48 Michael Hanselmann

496 3865ca48 Michael Hanselmann
  @type file_name: str
497 3865ca48 Michael Hanselmann
  @param file_name: file to be backed up
498 3865ca48 Michael Hanselmann
  @rtype: str
499 3865ca48 Michael Hanselmann
  @return: the path to the newly created backup
500 3865ca48 Michael Hanselmann
  @raise errors.ProgrammerError: for invalid file names
501 3865ca48 Michael Hanselmann

502 3865ca48 Michael Hanselmann
  """
503 3865ca48 Michael Hanselmann
  if not os.path.isfile(file_name):
504 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
505 3865ca48 Michael Hanselmann
                                file_name)
506 3865ca48 Michael Hanselmann
507 3865ca48 Michael Hanselmann
  prefix = ("%s.backup-%s." %
508 3865ca48 Michael Hanselmann
            (os.path.basename(file_name), TimestampForFilename()))
509 3865ca48 Michael Hanselmann
  dir_name = os.path.dirname(file_name)
510 3865ca48 Michael Hanselmann
511 d0c8c01d Iustin Pop
  fsrc = open(file_name, "rb")
512 3865ca48 Michael Hanselmann
  try:
513 3865ca48 Michael Hanselmann
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
514 d0c8c01d Iustin Pop
    fdst = os.fdopen(fd, "wb")
515 3865ca48 Michael Hanselmann
    try:
516 3865ca48 Michael Hanselmann
      logging.debug("Backing up %s at %s", file_name, backup_name)
517 3865ca48 Michael Hanselmann
      shutil.copyfileobj(fsrc, fdst)
518 3865ca48 Michael Hanselmann
    finally:
519 3865ca48 Michael Hanselmann
      fdst.close()
520 3865ca48 Michael Hanselmann
  finally:
521 3865ca48 Michael Hanselmann
    fsrc.close()
522 3865ca48 Michael Hanselmann
523 3865ca48 Michael Hanselmann
  return backup_name
524 3865ca48 Michael Hanselmann
525 3865ca48 Michael Hanselmann
526 3865ca48 Michael Hanselmann
def ListVisibleFiles(path):
527 3865ca48 Michael Hanselmann
  """Returns a list of visible files in a directory.
528 3865ca48 Michael Hanselmann

529 3865ca48 Michael Hanselmann
  @type path: str
530 3865ca48 Michael Hanselmann
  @param path: the directory to enumerate
531 3865ca48 Michael Hanselmann
  @rtype: list
532 3865ca48 Michael Hanselmann
  @return: the list of all files not starting with a dot
533 3865ca48 Michael Hanselmann
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
534 3865ca48 Michael Hanselmann

535 3865ca48 Michael Hanselmann
  """
536 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(path):
537 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
538 3865ca48 Michael Hanselmann
                                 " absolute/normalized: '%s'" % path)
539 3865ca48 Michael Hanselmann
  files = [i for i in os.listdir(path) if not i.startswith(".")]
540 3865ca48 Michael Hanselmann
  return files
541 3865ca48 Michael Hanselmann
542 3865ca48 Michael Hanselmann
543 3865ca48 Michael Hanselmann
def EnsureDirs(dirs):
544 3865ca48 Michael Hanselmann
  """Make required directories, if they don't exist.
545 3865ca48 Michael Hanselmann

546 3865ca48 Michael Hanselmann
  @param dirs: list of tuples (dir_name, dir_mode)
547 3865ca48 Michael Hanselmann
  @type dirs: list of (string, integer)
548 3865ca48 Michael Hanselmann

549 3865ca48 Michael Hanselmann
  """
550 3865ca48 Michael Hanselmann
  for dir_name, dir_mode in dirs:
551 3865ca48 Michael Hanselmann
    try:
552 3865ca48 Michael Hanselmann
      os.mkdir(dir_name, dir_mode)
553 3865ca48 Michael Hanselmann
    except EnvironmentError, err:
554 3865ca48 Michael Hanselmann
      if err.errno != errno.EEXIST:
555 3865ca48 Michael Hanselmann
        raise errors.GenericError("Cannot create needed directory"
556 3865ca48 Michael Hanselmann
                                  " '%s': %s" % (dir_name, err))
557 3865ca48 Michael Hanselmann
    try:
558 3865ca48 Michael Hanselmann
      os.chmod(dir_name, dir_mode)
559 3865ca48 Michael Hanselmann
    except EnvironmentError, err:
560 3865ca48 Michael Hanselmann
      raise errors.GenericError("Cannot change directory permissions on"
561 3865ca48 Michael Hanselmann
                                " '%s': %s" % (dir_name, err))
562 3865ca48 Michael Hanselmann
    if not os.path.isdir(dir_name):
563 3865ca48 Michael Hanselmann
      raise errors.GenericError("%s is not a directory" % dir_name)
564 3865ca48 Michael Hanselmann
565 3865ca48 Michael Hanselmann
566 3865ca48 Michael Hanselmann
def FindFile(name, search_path, test=os.path.exists):
567 3865ca48 Michael Hanselmann
  """Look for a filesystem object in a given path.
568 3865ca48 Michael Hanselmann

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

572 3865ca48 Michael Hanselmann
  @type name: str
573 3865ca48 Michael Hanselmann
  @param name: the name to look for
574 3865ca48 Michael Hanselmann
  @type search_path: str
575 3865ca48 Michael Hanselmann
  @param search_path: location to start at
576 3865ca48 Michael Hanselmann
  @type test: callable
577 3865ca48 Michael Hanselmann
  @param test: a function taking one argument that should return True
578 3865ca48 Michael Hanselmann
      if the a given object is valid; the default value is
579 3865ca48 Michael Hanselmann
      os.path.exists, causing only existing files to be returned
580 3865ca48 Michael Hanselmann
  @rtype: str or None
581 3865ca48 Michael Hanselmann
  @return: full path to the object if found, None otherwise
582 3865ca48 Michael Hanselmann

583 3865ca48 Michael Hanselmann
  """
584 3865ca48 Michael Hanselmann
  # validate the filename mask
585 3865ca48 Michael Hanselmann
  if constants.EXT_PLUGIN_MASK.match(name) is None:
586 3865ca48 Michael Hanselmann
    logging.critical("Invalid value passed for external script name: '%s'",
587 3865ca48 Michael Hanselmann
                     name)
588 3865ca48 Michael Hanselmann
    return None
589 3865ca48 Michael Hanselmann
590 3865ca48 Michael Hanselmann
  for dir_name in search_path:
591 3865ca48 Michael Hanselmann
    # FIXME: investigate switch to PathJoin
592 3865ca48 Michael Hanselmann
    item_name = os.path.sep.join([dir_name, name])
593 3865ca48 Michael Hanselmann
    # check the user test and that we're indeed resolving to the given
594 3865ca48 Michael Hanselmann
    # basename
595 3865ca48 Michael Hanselmann
    if test(item_name) and os.path.basename(item_name) == name:
596 3865ca48 Michael Hanselmann
      return item_name
597 3865ca48 Michael Hanselmann
  return None
598 3865ca48 Michael Hanselmann
599 3865ca48 Michael Hanselmann
600 3865ca48 Michael Hanselmann
def IsNormAbsPath(path):
601 3865ca48 Michael Hanselmann
  """Check whether a path is absolute and also normalized
602 3865ca48 Michael Hanselmann

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

605 3865ca48 Michael Hanselmann
  """
606 3865ca48 Michael Hanselmann
  return os.path.normpath(path) == path and os.path.isabs(path)
607 3865ca48 Michael Hanselmann
608 3865ca48 Michael Hanselmann
609 fb17bebd René Nussbaumer
def IsBelowDir(root, other_path):
610 fb17bebd René Nussbaumer
  """Check whether a path is below a root dir.
611 fb17bebd René Nussbaumer

612 fb17bebd René Nussbaumer
  This works around the nasty byte-byte comparisation of commonprefix.
613 fb17bebd René Nussbaumer

614 fb17bebd René Nussbaumer
  """
615 fb17bebd René Nussbaumer
  if not (os.path.isabs(root) and os.path.isabs(other_path)):
616 fb17bebd René Nussbaumer
    raise ValueError("Provided paths '%s' and '%s' are not absolute" %
617 fb17bebd René Nussbaumer
                     (root, other_path))
618 fb17bebd René Nussbaumer
  prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
619 fb17bebd René Nussbaumer
  return os.path.commonprefix([prepared_root,
620 fb17bebd René Nussbaumer
                               os.path.normpath(other_path)]) == prepared_root
621 fb17bebd René Nussbaumer
622 fb17bebd René Nussbaumer
623 3865ca48 Michael Hanselmann
def PathJoin(*args):
624 3865ca48 Michael Hanselmann
  """Safe-join a list of path components.
625 3865ca48 Michael Hanselmann

626 3865ca48 Michael Hanselmann
  Requirements:
627 3865ca48 Michael Hanselmann
      - the first argument must be an absolute path
628 3865ca48 Michael Hanselmann
      - no component in the path must have backtracking (e.g. /../),
629 3865ca48 Michael Hanselmann
        since we check for normalization at the end
630 3865ca48 Michael Hanselmann

631 3865ca48 Michael Hanselmann
  @param args: the path components to be joined
632 3865ca48 Michael Hanselmann
  @raise ValueError: for invalid paths
633 3865ca48 Michael Hanselmann

634 3865ca48 Michael Hanselmann
  """
635 3865ca48 Michael Hanselmann
  # ensure we're having at least one path passed in
636 3865ca48 Michael Hanselmann
  assert args
637 3865ca48 Michael Hanselmann
  # ensure the first component is an absolute and normalized path name
638 3865ca48 Michael Hanselmann
  root = args[0]
639 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(root):
640 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
641 3865ca48 Michael Hanselmann
  result = os.path.join(*args)
642 3865ca48 Michael Hanselmann
  # ensure that the whole path is normalized
643 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(result):
644 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
645 3865ca48 Michael Hanselmann
  # check that we're still under the original prefix
646 cf00dba0 René Nussbaumer
  if not IsBelowDir(root, result):
647 3865ca48 Michael Hanselmann
    raise ValueError("Error: path joining resulted in different prefix"
648 cf00dba0 René Nussbaumer
                     " (%s != %s)" % (result, root))
649 3865ca48 Michael Hanselmann
  return result
650 3865ca48 Michael Hanselmann
651 3865ca48 Michael Hanselmann
652 3865ca48 Michael Hanselmann
def TailFile(fname, lines=20):
653 3865ca48 Michael Hanselmann
  """Return the last lines from a file.
654 3865ca48 Michael Hanselmann

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

659 3865ca48 Michael Hanselmann
  @param fname: the file name
660 3865ca48 Michael Hanselmann
  @type lines: int
661 3865ca48 Michael Hanselmann
  @param lines: the (maximum) number of lines to return
662 3865ca48 Michael Hanselmann

663 3865ca48 Michael Hanselmann
  """
664 3865ca48 Michael Hanselmann
  fd = open(fname, "r")
665 3865ca48 Michael Hanselmann
  try:
666 3865ca48 Michael Hanselmann
    fd.seek(0, 2)
667 3865ca48 Michael Hanselmann
    pos = fd.tell()
668 e687ec01 Michael Hanselmann
    pos = max(0, pos - 4096)
669 3865ca48 Michael Hanselmann
    fd.seek(pos, 0)
670 3865ca48 Michael Hanselmann
    raw_data = fd.read()
671 3865ca48 Michael Hanselmann
  finally:
672 3865ca48 Michael Hanselmann
    fd.close()
673 3865ca48 Michael Hanselmann
674 3865ca48 Michael Hanselmann
  rows = raw_data.splitlines()
675 3865ca48 Michael Hanselmann
  return rows[-lines:]
676 3865ca48 Michael Hanselmann
677 3865ca48 Michael Hanselmann
678 3865ca48 Michael Hanselmann
def BytesToMebibyte(value):
679 3865ca48 Michael Hanselmann
  """Converts bytes to mebibytes.
680 3865ca48 Michael Hanselmann

681 3865ca48 Michael Hanselmann
  @type value: int
682 3865ca48 Michael Hanselmann
  @param value: Value in bytes
683 3865ca48 Michael Hanselmann
  @rtype: int
684 3865ca48 Michael Hanselmann
  @return: Value in mebibytes
685 3865ca48 Michael Hanselmann

686 3865ca48 Michael Hanselmann
  """
687 3865ca48 Michael Hanselmann
  return int(round(value / (1024.0 * 1024.0), 0))
688 3865ca48 Michael Hanselmann
689 3865ca48 Michael Hanselmann
690 3865ca48 Michael Hanselmann
def CalculateDirectorySize(path):
691 3865ca48 Michael Hanselmann
  """Calculates the size of a directory recursively.
692 3865ca48 Michael Hanselmann

693 3865ca48 Michael Hanselmann
  @type path: string
694 3865ca48 Michael Hanselmann
  @param path: Path to directory
695 3865ca48 Michael Hanselmann
  @rtype: int
696 3865ca48 Michael Hanselmann
  @return: Size in mebibytes
697 3865ca48 Michael Hanselmann

698 3865ca48 Michael Hanselmann
  """
699 3865ca48 Michael Hanselmann
  size = 0
700 3865ca48 Michael Hanselmann
701 3865ca48 Michael Hanselmann
  for (curpath, _, files) in os.walk(path):
702 3865ca48 Michael Hanselmann
    for filename in files:
703 3865ca48 Michael Hanselmann
      st = os.lstat(PathJoin(curpath, filename))
704 3865ca48 Michael Hanselmann
      size += st.st_size
705 3865ca48 Michael Hanselmann
706 3865ca48 Michael Hanselmann
  return BytesToMebibyte(size)
707 3865ca48 Michael Hanselmann
708 3865ca48 Michael Hanselmann
709 3865ca48 Michael Hanselmann
def GetFilesystemStats(path):
710 3865ca48 Michael Hanselmann
  """Returns the total and free space on a filesystem.
711 3865ca48 Michael Hanselmann

712 3865ca48 Michael Hanselmann
  @type path: string
713 3865ca48 Michael Hanselmann
  @param path: Path on filesystem to be examined
714 3865ca48 Michael Hanselmann
  @rtype: int
715 3865ca48 Michael Hanselmann
  @return: tuple of (Total space, Free space) in mebibytes
716 3865ca48 Michael Hanselmann

717 3865ca48 Michael Hanselmann
  """
718 3865ca48 Michael Hanselmann
  st = os.statvfs(path)
719 3865ca48 Michael Hanselmann
720 3865ca48 Michael Hanselmann
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
721 3865ca48 Michael Hanselmann
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
722 3865ca48 Michael Hanselmann
  return (tsize, fsize)
723 3865ca48 Michael Hanselmann
724 3865ca48 Michael Hanselmann
725 3865ca48 Michael Hanselmann
def ReadPidFile(pidfile):
726 3865ca48 Michael Hanselmann
  """Read a pid from a file.
727 3865ca48 Michael Hanselmann

728 3865ca48 Michael Hanselmann
  @type  pidfile: string
729 3865ca48 Michael Hanselmann
  @param pidfile: path to the file containing the pid
730 3865ca48 Michael Hanselmann
  @rtype: int
731 3865ca48 Michael Hanselmann
  @return: The process id, if the file exists and contains a valid PID,
732 3865ca48 Michael Hanselmann
           otherwise 0
733 3865ca48 Michael Hanselmann

734 3865ca48 Michael Hanselmann
  """
735 3865ca48 Michael Hanselmann
  try:
736 3865ca48 Michael Hanselmann
    raw_data = ReadOneLineFile(pidfile)
737 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
738 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
739 3865ca48 Michael Hanselmann
      logging.exception("Can't read pid file")
740 3865ca48 Michael Hanselmann
    return 0
741 3865ca48 Michael Hanselmann
742 3865ca48 Michael Hanselmann
  try:
743 3865ca48 Michael Hanselmann
    pid = int(raw_data)
744 3865ca48 Michael Hanselmann
  except (TypeError, ValueError), err:
745 3865ca48 Michael Hanselmann
    logging.info("Can't parse pid file contents", exc_info=True)
746 3865ca48 Michael Hanselmann
    return 0
747 3865ca48 Michael Hanselmann
748 3865ca48 Michael Hanselmann
  return pid
749 3865ca48 Michael Hanselmann
750 3865ca48 Michael Hanselmann
751 3865ca48 Michael Hanselmann
def ReadLockedPidFile(path):
752 3865ca48 Michael Hanselmann
  """Reads a locked PID file.
753 3865ca48 Michael Hanselmann

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

756 3865ca48 Michael Hanselmann
  @type path: string
757 3865ca48 Michael Hanselmann
  @param path: Path to PID file
758 3865ca48 Michael Hanselmann
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
759 3865ca48 Michael Hanselmann

760 3865ca48 Michael Hanselmann
  """
761 3865ca48 Michael Hanselmann
  try:
762 3865ca48 Michael Hanselmann
    fd = os.open(path, os.O_RDONLY)
763 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
764 3865ca48 Michael Hanselmann
    if err.errno == errno.ENOENT:
765 3865ca48 Michael Hanselmann
      # PID file doesn't exist
766 3865ca48 Michael Hanselmann
      return None
767 3865ca48 Michael Hanselmann
    raise
768 3865ca48 Michael Hanselmann
769 3865ca48 Michael Hanselmann
  try:
770 3865ca48 Michael Hanselmann
    try:
771 3865ca48 Michael Hanselmann
      # Try to acquire lock
772 3865ca48 Michael Hanselmann
      filelock.LockFile(fd)
773 3865ca48 Michael Hanselmann
    except errors.LockError:
774 3865ca48 Michael Hanselmann
      # Couldn't lock, daemon is running
775 3865ca48 Michael Hanselmann
      return int(os.read(fd, 100))
776 3865ca48 Michael Hanselmann
  finally:
777 3865ca48 Michael Hanselmann
    os.close(fd)
778 3865ca48 Michael Hanselmann
779 3865ca48 Michael Hanselmann
  return None
780 3865ca48 Michael Hanselmann
781 3865ca48 Michael Hanselmann
782 3865ca48 Michael Hanselmann
def AddAuthorizedKey(file_obj, key):
783 3865ca48 Michael Hanselmann
  """Adds an SSH public key to an authorized_keys file.
784 3865ca48 Michael Hanselmann

785 3865ca48 Michael Hanselmann
  @type file_obj: str or file handle
786 3865ca48 Michael Hanselmann
  @param file_obj: path to authorized_keys file
787 3865ca48 Michael Hanselmann
  @type key: str
788 3865ca48 Michael Hanselmann
  @param key: string containing key
789 3865ca48 Michael Hanselmann

790 3865ca48 Michael Hanselmann
  """
791 3865ca48 Michael Hanselmann
  key_fields = key.split()
792 3865ca48 Michael Hanselmann
793 3865ca48 Michael Hanselmann
  if isinstance(file_obj, basestring):
794 d0c8c01d Iustin Pop
    f = open(file_obj, "a+")
795 3865ca48 Michael Hanselmann
  else:
796 3865ca48 Michael Hanselmann
    f = file_obj
797 3865ca48 Michael Hanselmann
798 3865ca48 Michael Hanselmann
  try:
799 3865ca48 Michael Hanselmann
    nl = True
800 3865ca48 Michael Hanselmann
    for line in f:
801 3865ca48 Michael Hanselmann
      # Ignore whitespace changes
802 3865ca48 Michael Hanselmann
      if line.split() == key_fields:
803 3865ca48 Michael Hanselmann
        break
804 d0c8c01d Iustin Pop
      nl = line.endswith("\n")
805 3865ca48 Michael Hanselmann
    else:
806 3865ca48 Michael Hanselmann
      if not nl:
807 3865ca48 Michael Hanselmann
        f.write("\n")
808 d0c8c01d Iustin Pop
      f.write(key.rstrip("\r\n"))
809 3865ca48 Michael Hanselmann
      f.write("\n")
810 3865ca48 Michael Hanselmann
      f.flush()
811 3865ca48 Michael Hanselmann
  finally:
812 3865ca48 Michael Hanselmann
    f.close()
813 3865ca48 Michael Hanselmann
814 3865ca48 Michael Hanselmann
815 3865ca48 Michael Hanselmann
def RemoveAuthorizedKey(file_name, key):
816 3865ca48 Michael Hanselmann
  """Removes an SSH public key from an authorized_keys file.
817 3865ca48 Michael Hanselmann

818 3865ca48 Michael Hanselmann
  @type file_name: str
819 3865ca48 Michael Hanselmann
  @param file_name: path to authorized_keys file
820 3865ca48 Michael Hanselmann
  @type key: str
821 3865ca48 Michael Hanselmann
  @param key: string containing key
822 3865ca48 Michael Hanselmann

823 3865ca48 Michael Hanselmann
  """
824 3865ca48 Michael Hanselmann
  key_fields = key.split()
825 3865ca48 Michael Hanselmann
826 3865ca48 Michael Hanselmann
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
827 3865ca48 Michael Hanselmann
  try:
828 d0c8c01d Iustin Pop
    out = os.fdopen(fd, "w")
829 3865ca48 Michael Hanselmann
    try:
830 d0c8c01d Iustin Pop
      f = open(file_name, "r")
831 3865ca48 Michael Hanselmann
      try:
832 3865ca48 Michael Hanselmann
        for line in f:
833 3865ca48 Michael Hanselmann
          # Ignore whitespace changes while comparing lines
834 3865ca48 Michael Hanselmann
          if line.split() != key_fields:
835 3865ca48 Michael Hanselmann
            out.write(line)
836 3865ca48 Michael Hanselmann
837 3865ca48 Michael Hanselmann
        out.flush()
838 3865ca48 Michael Hanselmann
        os.rename(tmpname, file_name)
839 3865ca48 Michael Hanselmann
      finally:
840 3865ca48 Michael Hanselmann
        f.close()
841 3865ca48 Michael Hanselmann
    finally:
842 3865ca48 Michael Hanselmann
      out.close()
843 3865ca48 Michael Hanselmann
  except:
844 3865ca48 Michael Hanselmann
    RemoveFile(tmpname)
845 3865ca48 Michael Hanselmann
    raise
846 3865ca48 Michael Hanselmann
847 3865ca48 Michael Hanselmann
848 3865ca48 Michael Hanselmann
def DaemonPidFileName(name):
849 3865ca48 Michael Hanselmann
  """Compute a ganeti pid file absolute path
850 3865ca48 Michael Hanselmann

851 3865ca48 Michael Hanselmann
  @type name: str
852 3865ca48 Michael Hanselmann
  @param name: the daemon name
853 3865ca48 Michael Hanselmann
  @rtype: str
854 3865ca48 Michael Hanselmann
  @return: the full path to the pidfile corresponding to the given
855 3865ca48 Michael Hanselmann
      daemon name
856 3865ca48 Michael Hanselmann

857 3865ca48 Michael Hanselmann
  """
858 3865ca48 Michael Hanselmann
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
859 3865ca48 Michael Hanselmann
860 3865ca48 Michael Hanselmann
861 3865ca48 Michael Hanselmann
def WritePidFile(pidfile):
862 3865ca48 Michael Hanselmann
  """Write the current process pidfile.
863 3865ca48 Michael Hanselmann

864 3865ca48 Michael Hanselmann
  @type pidfile: string
865 3865ca48 Michael Hanselmann
  @param pidfile: the path to the file to be written
866 3865ca48 Michael Hanselmann
  @raise errors.LockError: if the pid file already exists and
867 3865ca48 Michael Hanselmann
      points to a live process
868 3865ca48 Michael Hanselmann
  @rtype: int
869 3865ca48 Michael Hanselmann
  @return: the file descriptor of the lock file; do not close this unless
870 3865ca48 Michael Hanselmann
      you want to unlock the pid file
871 3865ca48 Michael Hanselmann

872 3865ca48 Michael Hanselmann
  """
873 3865ca48 Michael Hanselmann
  # We don't rename nor truncate the file to not drop locks under
874 3865ca48 Michael Hanselmann
  # existing processes
875 3865ca48 Michael Hanselmann
  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
876 3865ca48 Michael Hanselmann
877 3865ca48 Michael Hanselmann
  # Lock the PID file (and fail if not possible to do so). Any code
878 3865ca48 Michael Hanselmann
  # wanting to send a signal to the daemon should try to lock the PID
879 3865ca48 Michael Hanselmann
  # file before reading it. If acquiring the lock succeeds, the daemon is
880 3865ca48 Michael Hanselmann
  # no longer running and the signal should not be sent.
881 3865ca48 Michael Hanselmann
  filelock.LockFile(fd_pidfile)
882 3865ca48 Michael Hanselmann
883 3865ca48 Michael Hanselmann
  os.write(fd_pidfile, "%d\n" % os.getpid())
884 3865ca48 Michael Hanselmann
885 3865ca48 Michael Hanselmann
  return fd_pidfile
886 3865ca48 Michael Hanselmann
887 3865ca48 Michael Hanselmann
888 3865ca48 Michael Hanselmann
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
889 3865ca48 Michael Hanselmann
  """Reads the watcher pause file.
890 3865ca48 Michael Hanselmann

891 3865ca48 Michael Hanselmann
  @type filename: string
892 3865ca48 Michael Hanselmann
  @param filename: Path to watcher pause file
893 3865ca48 Michael Hanselmann
  @type now: None, float or int
894 3865ca48 Michael Hanselmann
  @param now: Current time as Unix timestamp
895 3865ca48 Michael Hanselmann
  @type remove_after: int
896 3865ca48 Michael Hanselmann
  @param remove_after: Remove watcher pause file after specified amount of
897 3865ca48 Michael Hanselmann
    seconds past the pause end time
898 3865ca48 Michael Hanselmann

899 3865ca48 Michael Hanselmann
  """
900 3865ca48 Michael Hanselmann
  if now is None:
901 3865ca48 Michael Hanselmann
    now = time.time()
902 3865ca48 Michael Hanselmann
903 3865ca48 Michael Hanselmann
  try:
904 3865ca48 Michael Hanselmann
    value = ReadFile(filename)
905 3865ca48 Michael Hanselmann
  except IOError, err:
906 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
907 3865ca48 Michael Hanselmann
      raise
908 3865ca48 Michael Hanselmann
    value = None
909 3865ca48 Michael Hanselmann
910 3865ca48 Michael Hanselmann
  if value is not None:
911 3865ca48 Michael Hanselmann
    try:
912 3865ca48 Michael Hanselmann
      value = int(value)
913 3865ca48 Michael Hanselmann
    except ValueError:
914 3865ca48 Michael Hanselmann
      logging.warning(("Watcher pause file (%s) contains invalid value,"
915 3865ca48 Michael Hanselmann
                       " removing it"), filename)
916 3865ca48 Michael Hanselmann
      RemoveFile(filename)
917 3865ca48 Michael Hanselmann
      value = None
918 3865ca48 Michael Hanselmann
919 3865ca48 Michael Hanselmann
    if value is not None:
920 3865ca48 Michael Hanselmann
      # Remove file if it's outdated
921 3865ca48 Michael Hanselmann
      if now > (value + remove_after):
922 3865ca48 Michael Hanselmann
        RemoveFile(filename)
923 3865ca48 Michael Hanselmann
        value = None
924 3865ca48 Michael Hanselmann
925 3865ca48 Michael Hanselmann
      elif now > value:
926 3865ca48 Michael Hanselmann
        value = None
927 3865ca48 Michael Hanselmann
928 3865ca48 Michael Hanselmann
  return value
929 90e234a6 Michael Hanselmann
930 90e234a6 Michael Hanselmann
931 90e234a6 Michael Hanselmann
def NewUUID():
932 90e234a6 Michael Hanselmann
  """Returns a random UUID.
933 90e234a6 Michael Hanselmann

934 90e234a6 Michael Hanselmann
  @note: This is a Linux-specific method as it uses the /proc
935 90e234a6 Michael Hanselmann
      filesystem.
936 90e234a6 Michael Hanselmann
  @rtype: str
937 90e234a6 Michael Hanselmann

938 90e234a6 Michael Hanselmann
  """
939 90e234a6 Michael Hanselmann
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
940 0c1a5b1e Agata Murawska
941 0c1a5b1e Agata Murawska
942 0c1a5b1e Agata Murawska
class TemporaryFileManager(object):
943 0c1a5b1e Agata Murawska
  """Stores the list of files to be deleted and removes them on demand.
944 0c1a5b1e Agata Murawska

945 0c1a5b1e Agata Murawska
  """
946 0c1a5b1e Agata Murawska
947 0c1a5b1e Agata Murawska
  def __init__(self):
948 0c1a5b1e Agata Murawska
    self._files = []
949 0c1a5b1e Agata Murawska
950 0c1a5b1e Agata Murawska
  def __del__(self):
951 0c1a5b1e Agata Murawska
    self.Cleanup()
952 0c1a5b1e Agata Murawska
953 0c1a5b1e Agata Murawska
  def Add(self, filename):
954 0c1a5b1e Agata Murawska
    """Add file to list of files to be deleted.
955 0c1a5b1e Agata Murawska

956 0c1a5b1e Agata Murawska
    @type filename: string
957 0c1a5b1e Agata Murawska
    @param filename: path to filename to be added
958 0c1a5b1e Agata Murawska

959 0c1a5b1e Agata Murawska
    """
960 0c1a5b1e Agata Murawska
    self._files.append(filename)
961 0c1a5b1e Agata Murawska
962 0c1a5b1e Agata Murawska
  def Remove(self, filename):
963 0c1a5b1e Agata Murawska
    """Remove file from list of files to be deleted.
964 0c1a5b1e Agata Murawska

965 0c1a5b1e Agata Murawska
    @type filename: string
966 0c1a5b1e Agata Murawska
    @param filename: path to filename to be deleted
967 0c1a5b1e Agata Murawska

968 0c1a5b1e Agata Murawska
    """
969 0c1a5b1e Agata Murawska
    self._files.remove(filename)
970 0c1a5b1e Agata Murawska
971 0c1a5b1e Agata Murawska
  def Cleanup(self):
972 0c1a5b1e Agata Murawska
    """Delete all files marked for deletion
973 0c1a5b1e Agata Murawska

974 0c1a5b1e Agata Murawska
    """
975 0c1a5b1e Agata Murawska
    while self._files:
976 0c1a5b1e Agata Murawska
      RemoveFile(self._files.pop())