Statistics
| Branch: | Tag: | Revision:

root / lib / utils / io.py @ c85b15c1

History | View | Annotate | Download (25.2 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 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 90e234a6 Michael Hanselmann
46 0e5084ee Michael Hanselmann
def ReadFile(file_name, size=-1, preread=None):
47 3865ca48 Michael Hanselmann
  """Reads a file.
48 3865ca48 Michael Hanselmann

49 3865ca48 Michael Hanselmann
  @type size: int
50 3865ca48 Michael Hanselmann
  @param size: Read at most size bytes (if negative, entire file)
51 0e5084ee Michael Hanselmann
  @type preread: callable receiving file handle as single parameter
52 0e5084ee Michael Hanselmann
  @param preread: Function called before file is read
53 3865ca48 Michael Hanselmann
  @rtype: str
54 3865ca48 Michael Hanselmann
  @return: the (possibly partial) content of the file
55 3865ca48 Michael Hanselmann

56 3865ca48 Michael Hanselmann
  """
57 3865ca48 Michael Hanselmann
  f = open(file_name, "r")
58 3865ca48 Michael Hanselmann
  try:
59 0e5084ee Michael Hanselmann
    if preread:
60 0e5084ee Michael Hanselmann
      preread(f)
61 0e5084ee Michael Hanselmann
62 3865ca48 Michael Hanselmann
    return f.read(size)
63 3865ca48 Michael Hanselmann
  finally:
64 3865ca48 Michael Hanselmann
    f.close()
65 3865ca48 Michael Hanselmann
66 3865ca48 Michael Hanselmann
67 3865ca48 Michael Hanselmann
def WriteFile(file_name, fn=None, data=None,
68 3865ca48 Michael Hanselmann
              mode=None, uid=-1, gid=-1,
69 3865ca48 Michael Hanselmann
              atime=None, mtime=None, close=True,
70 3865ca48 Michael Hanselmann
              dry_run=False, backup=False,
71 3865ca48 Michael Hanselmann
              prewrite=None, postwrite=None):
72 3865ca48 Michael Hanselmann
  """(Over)write a file atomically.
73 3865ca48 Michael Hanselmann

74 3865ca48 Michael Hanselmann
  The file_name and either fn (a function taking one argument, the
75 3865ca48 Michael Hanselmann
  file descriptor, and which should write the data to it) or data (the
76 3865ca48 Michael Hanselmann
  contents of the file) must be passed. The other arguments are
77 3865ca48 Michael Hanselmann
  optional and allow setting the file mode, owner and group, and the
78 3865ca48 Michael Hanselmann
  mtime/atime of the file.
79 3865ca48 Michael Hanselmann

80 3865ca48 Michael Hanselmann
  If the function doesn't raise an exception, it has succeeded and the
81 3865ca48 Michael Hanselmann
  target file has the new contents. If the function has raised an
82 3865ca48 Michael Hanselmann
  exception, an existing target file should be unmodified and the
83 3865ca48 Michael Hanselmann
  temporary file should be removed.
84 3865ca48 Michael Hanselmann

85 3865ca48 Michael Hanselmann
  @type file_name: str
86 3865ca48 Michael Hanselmann
  @param file_name: the target filename
87 3865ca48 Michael Hanselmann
  @type fn: callable
88 3865ca48 Michael Hanselmann
  @param fn: content writing function, called with
89 3865ca48 Michael Hanselmann
      file descriptor as parameter
90 3865ca48 Michael Hanselmann
  @type data: str
91 3865ca48 Michael Hanselmann
  @param data: contents of the file
92 3865ca48 Michael Hanselmann
  @type mode: int
93 3865ca48 Michael Hanselmann
  @param mode: file mode
94 3865ca48 Michael Hanselmann
  @type uid: int
95 3865ca48 Michael Hanselmann
  @param uid: the owner of the file
96 3865ca48 Michael Hanselmann
  @type gid: int
97 3865ca48 Michael Hanselmann
  @param gid: the group of the file
98 3865ca48 Michael Hanselmann
  @type atime: int
99 3865ca48 Michael Hanselmann
  @param atime: a custom access time to be set on the file
100 3865ca48 Michael Hanselmann
  @type mtime: int
101 3865ca48 Michael Hanselmann
  @param mtime: a custom modification time to be set on the file
102 3865ca48 Michael Hanselmann
  @type close: boolean
103 3865ca48 Michael Hanselmann
  @param close: whether to close file after writing it
104 3865ca48 Michael Hanselmann
  @type prewrite: callable
105 3865ca48 Michael Hanselmann
  @param prewrite: function to be called before writing content
106 3865ca48 Michael Hanselmann
  @type postwrite: callable
107 3865ca48 Michael Hanselmann
  @param postwrite: function to be called after writing content
108 3865ca48 Michael Hanselmann

109 3865ca48 Michael Hanselmann
  @rtype: None or int
110 3865ca48 Michael Hanselmann
  @return: None if the 'close' parameter evaluates to True,
111 3865ca48 Michael Hanselmann
      otherwise the file descriptor
112 3865ca48 Michael Hanselmann

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

115 3865ca48 Michael Hanselmann
  """
116 3865ca48 Michael Hanselmann
  if not os.path.isabs(file_name):
117 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Path passed to WriteFile is not"
118 3865ca48 Michael Hanselmann
                                 " absolute: '%s'" % file_name)
119 3865ca48 Michael Hanselmann
120 3865ca48 Michael Hanselmann
  if [fn, data].count(None) != 1:
121 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("fn or data required")
122 3865ca48 Michael Hanselmann
123 3865ca48 Michael Hanselmann
  if [atime, mtime].count(None) == 1:
124 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Both atime and mtime must be either"
125 3865ca48 Michael Hanselmann
                                 " set or None")
126 3865ca48 Michael Hanselmann
127 3865ca48 Michael Hanselmann
  if backup and not dry_run and os.path.isfile(file_name):
128 3865ca48 Michael Hanselmann
    CreateBackup(file_name)
129 3865ca48 Michael Hanselmann
130 a9d68e40 Michael Hanselmann
  # Whether temporary file needs to be removed (e.g. if any error occurs)
131 3865ca48 Michael Hanselmann
  do_remove = True
132 a9d68e40 Michael Hanselmann
133 a9d68e40 Michael Hanselmann
  # Function result
134 a9d68e40 Michael Hanselmann
  result = None
135 a9d68e40 Michael Hanselmann
136 a9d68e40 Michael Hanselmann
  (dir_name, base_name) = os.path.split(file_name)
137 a9d68e40 Michael Hanselmann
  (fd, new_name) = tempfile.mkstemp(suffix=".new", prefix=base_name,
138 a9d68e40 Michael Hanselmann
                                    dir=dir_name)
139 3865ca48 Michael Hanselmann
  try:
140 a9d68e40 Michael Hanselmann
    try:
141 a9d68e40 Michael Hanselmann
      if uid != -1 or gid != -1:
142 a9d68e40 Michael Hanselmann
        os.chown(new_name, uid, gid)
143 a9d68e40 Michael Hanselmann
      if mode:
144 a9d68e40 Michael Hanselmann
        os.chmod(new_name, mode)
145 a9d68e40 Michael Hanselmann
      if callable(prewrite):
146 a9d68e40 Michael Hanselmann
        prewrite(fd)
147 a9d68e40 Michael Hanselmann
      if data is not None:
148 1d39e245 Iustin Pop
        if isinstance(data, unicode):
149 1d39e245 Iustin Pop
          data = data.encode()
150 1d39e245 Iustin Pop
        assert isinstance(data, str)
151 437c3e77 Iustin Pop
        to_write = len(data)
152 437c3e77 Iustin Pop
        offset = 0
153 437c3e77 Iustin Pop
        while offset < to_write:
154 437c3e77 Iustin Pop
          written = os.write(fd, buffer(data, offset))
155 437c3e77 Iustin Pop
          assert written >= 0
156 1d39e245 Iustin Pop
          assert written <= to_write - offset
157 437c3e77 Iustin Pop
          offset += written
158 437c3e77 Iustin Pop
        assert offset == to_write
159 a9d68e40 Michael Hanselmann
      else:
160 a9d68e40 Michael Hanselmann
        fn(fd)
161 a9d68e40 Michael Hanselmann
      if callable(postwrite):
162 a9d68e40 Michael Hanselmann
        postwrite(fd)
163 a9d68e40 Michael Hanselmann
      os.fsync(fd)
164 a9d68e40 Michael Hanselmann
      if atime is not None and mtime is not None:
165 a9d68e40 Michael Hanselmann
        os.utime(new_name, (atime, mtime))
166 a9d68e40 Michael Hanselmann
    finally:
167 a9d68e40 Michael Hanselmann
      # Close file unless the file descriptor should be returned
168 a9d68e40 Michael Hanselmann
      if close:
169 a9d68e40 Michael Hanselmann
        os.close(fd)
170 a9d68e40 Michael Hanselmann
      else:
171 a9d68e40 Michael Hanselmann
        result = fd
172 a9d68e40 Michael Hanselmann
173 a9d68e40 Michael Hanselmann
    # Rename file to destination name
174 3865ca48 Michael Hanselmann
    if not dry_run:
175 3865ca48 Michael Hanselmann
      os.rename(new_name, file_name)
176 a9d68e40 Michael Hanselmann
      # Successful, no need to remove anymore
177 3865ca48 Michael Hanselmann
      do_remove = False
178 3865ca48 Michael Hanselmann
  finally:
179 3865ca48 Michael Hanselmann
    if do_remove:
180 3865ca48 Michael Hanselmann
      RemoveFile(new_name)
181 3865ca48 Michael Hanselmann
182 3865ca48 Michael Hanselmann
  return result
183 3865ca48 Michael Hanselmann
184 3865ca48 Michael Hanselmann
185 3865ca48 Michael Hanselmann
def GetFileID(path=None, fd=None):
186 3865ca48 Michael Hanselmann
  """Returns the file 'id', i.e. the dev/inode and mtime information.
187 3865ca48 Michael Hanselmann

188 3865ca48 Michael Hanselmann
  Either the path to the file or the fd must be given.
189 3865ca48 Michael Hanselmann

190 3865ca48 Michael Hanselmann
  @param path: the file path
191 3865ca48 Michael Hanselmann
  @param fd: a file descriptor
192 3865ca48 Michael Hanselmann
  @return: a tuple of (device number, inode number, mtime)
193 3865ca48 Michael Hanselmann

194 3865ca48 Michael Hanselmann
  """
195 3865ca48 Michael Hanselmann
  if [path, fd].count(None) != 1:
196 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("One and only one of fd/path must be given")
197 3865ca48 Michael Hanselmann
198 3865ca48 Michael Hanselmann
  if fd is None:
199 3865ca48 Michael Hanselmann
    st = os.stat(path)
200 3865ca48 Michael Hanselmann
  else:
201 3865ca48 Michael Hanselmann
    st = os.fstat(fd)
202 3865ca48 Michael Hanselmann
203 3865ca48 Michael Hanselmann
  return (st.st_dev, st.st_ino, st.st_mtime)
204 3865ca48 Michael Hanselmann
205 3865ca48 Michael Hanselmann
206 3865ca48 Michael Hanselmann
def VerifyFileID(fi_disk, fi_ours):
207 3865ca48 Michael Hanselmann
  """Verifies that two file IDs are matching.
208 3865ca48 Michael Hanselmann

209 3865ca48 Michael Hanselmann
  Differences in the inode/device are not accepted, but and older
210 3865ca48 Michael Hanselmann
  timestamp for fi_disk is accepted.
211 3865ca48 Michael Hanselmann

212 3865ca48 Michael Hanselmann
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
213 3865ca48 Michael Hanselmann
      file data
214 3865ca48 Michael Hanselmann
  @param fi_ours: tuple (dev, inode, mtime) representing the last
215 3865ca48 Michael Hanselmann
      written file data
216 3865ca48 Michael Hanselmann
  @rtype: boolean
217 3865ca48 Michael Hanselmann

218 3865ca48 Michael Hanselmann
  """
219 3865ca48 Michael Hanselmann
  (d1, i1, m1) = fi_disk
220 3865ca48 Michael Hanselmann
  (d2, i2, m2) = fi_ours
221 3865ca48 Michael Hanselmann
222 3865ca48 Michael Hanselmann
  return (d1, i1) == (d2, i2) and m1 <= m2
223 3865ca48 Michael Hanselmann
224 3865ca48 Michael Hanselmann
225 3865ca48 Michael Hanselmann
def SafeWriteFile(file_name, file_id, **kwargs):
226 3865ca48 Michael Hanselmann
  """Wraper over L{WriteFile} that locks the target file.
227 3865ca48 Michael Hanselmann

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

231 3865ca48 Michael Hanselmann
  @type file_name: str
232 3865ca48 Michael Hanselmann
  @param file_name: the target filename
233 3865ca48 Michael Hanselmann
  @type file_id: tuple
234 3865ca48 Michael Hanselmann
  @param file_id: a result from L{GetFileID}
235 3865ca48 Michael Hanselmann

236 3865ca48 Michael Hanselmann
  """
237 3865ca48 Michael Hanselmann
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
238 3865ca48 Michael Hanselmann
  try:
239 3865ca48 Michael Hanselmann
    filelock.LockFile(fd)
240 3865ca48 Michael Hanselmann
    if file_id is not None:
241 3865ca48 Michael Hanselmann
      disk_id = GetFileID(fd=fd)
242 3865ca48 Michael Hanselmann
      if not VerifyFileID(disk_id, file_id):
243 3865ca48 Michael Hanselmann
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
244 3865ca48 Michael Hanselmann
                               " since last written" % file_name)
245 3865ca48 Michael Hanselmann
    return WriteFile(file_name, **kwargs)
246 3865ca48 Michael Hanselmann
  finally:
247 3865ca48 Michael Hanselmann
    os.close(fd)
248 3865ca48 Michael Hanselmann
249 3865ca48 Michael Hanselmann
250 3865ca48 Michael Hanselmann
def ReadOneLineFile(file_name, strict=False):
251 3865ca48 Michael Hanselmann
  """Return the first non-empty line from a file.
252 3865ca48 Michael Hanselmann

253 3865ca48 Michael Hanselmann
  @type strict: boolean
254 3865ca48 Michael Hanselmann
  @param strict: if True, abort if the file has more than one
255 3865ca48 Michael Hanselmann
      non-empty line
256 3865ca48 Michael Hanselmann

257 3865ca48 Michael Hanselmann
  """
258 3865ca48 Michael Hanselmann
  file_lines = ReadFile(file_name).splitlines()
259 3865ca48 Michael Hanselmann
  full_lines = filter(bool, file_lines)
260 3865ca48 Michael Hanselmann
  if not file_lines or not full_lines:
261 3865ca48 Michael Hanselmann
    raise errors.GenericError("No data in one-liner file %s" % file_name)
262 3865ca48 Michael Hanselmann
  elif strict and len(full_lines) > 1:
263 3865ca48 Michael Hanselmann
    raise errors.GenericError("Too many lines in one-liner file %s" %
264 3865ca48 Michael Hanselmann
                              file_name)
265 3865ca48 Michael Hanselmann
  return full_lines[0]
266 3865ca48 Michael Hanselmann
267 3865ca48 Michael Hanselmann
268 3865ca48 Michael Hanselmann
def RemoveFile(filename):
269 3865ca48 Michael Hanselmann
  """Remove a file ignoring some errors.
270 3865ca48 Michael Hanselmann

271 3865ca48 Michael Hanselmann
  Remove a file, ignoring non-existing ones or directories. Other
272 3865ca48 Michael Hanselmann
  errors are passed.
273 3865ca48 Michael Hanselmann

274 3865ca48 Michael Hanselmann
  @type filename: str
275 3865ca48 Michael Hanselmann
  @param filename: the file to be removed
276 3865ca48 Michael Hanselmann

277 3865ca48 Michael Hanselmann
  """
278 3865ca48 Michael Hanselmann
  try:
279 3865ca48 Michael Hanselmann
    os.unlink(filename)
280 3865ca48 Michael Hanselmann
  except OSError, err:
281 3865ca48 Michael Hanselmann
    if err.errno not in (errno.ENOENT, errno.EISDIR):
282 3865ca48 Michael Hanselmann
      raise
283 3865ca48 Michael Hanselmann
284 3865ca48 Michael Hanselmann
285 3865ca48 Michael Hanselmann
def RemoveDir(dirname):
286 3865ca48 Michael Hanselmann
  """Remove an empty directory.
287 3865ca48 Michael Hanselmann

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

292 3865ca48 Michael Hanselmann
  @type dirname: str
293 3865ca48 Michael Hanselmann
  @param dirname: the empty directory to be removed
294 3865ca48 Michael Hanselmann

295 3865ca48 Michael Hanselmann
  """
296 3865ca48 Michael Hanselmann
  try:
297 3865ca48 Michael Hanselmann
    os.rmdir(dirname)
298 3865ca48 Michael Hanselmann
  except OSError, err:
299 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
300 3865ca48 Michael Hanselmann
      raise
301 3865ca48 Michael Hanselmann
302 3865ca48 Michael Hanselmann
303 8e5a705d René Nussbaumer
def RenameFile(old, new, mkdir=False, mkdir_mode=0750, dir_uid=None,
304 8e5a705d René Nussbaumer
               dir_gid=None):
305 3865ca48 Michael Hanselmann
  """Renames a file.
306 3865ca48 Michael Hanselmann

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

310 3865ca48 Michael Hanselmann
  @type old: string
311 3865ca48 Michael Hanselmann
  @param old: Original path
312 3865ca48 Michael Hanselmann
  @type new: string
313 3865ca48 Michael Hanselmann
  @param new: New path
314 3865ca48 Michael Hanselmann
  @type mkdir: bool
315 3865ca48 Michael Hanselmann
  @param mkdir: Whether to create target directory if it doesn't exist
316 3865ca48 Michael Hanselmann
  @type mkdir_mode: int
317 3865ca48 Michael Hanselmann
  @param mkdir_mode: Mode for newly created directories
318 8e5a705d René Nussbaumer
  @type dir_uid: int
319 8e5a705d René Nussbaumer
  @param dir_uid: The uid for the (if fresh created) dir
320 8e5a705d René Nussbaumer
  @type dir_gid: int
321 8e5a705d René Nussbaumer
  @param dir_gid: The gid for the (if fresh created) dir
322 3865ca48 Michael Hanselmann

323 3865ca48 Michael Hanselmann
  """
324 3865ca48 Michael Hanselmann
  try:
325 3865ca48 Michael Hanselmann
    return os.rename(old, new)
326 3865ca48 Michael Hanselmann
  except OSError, err:
327 3865ca48 Michael Hanselmann
    # In at least one use case of this function, the job queue, directory
328 3865ca48 Michael Hanselmann
    # creation is very rare. Checking for the directory before renaming is not
329 3865ca48 Michael Hanselmann
    # as efficient.
330 3865ca48 Michael Hanselmann
    if mkdir and err.errno == errno.ENOENT:
331 3865ca48 Michael Hanselmann
      # Create directory and try again
332 8e5a705d René Nussbaumer
      dir_path = os.path.dirname(new)
333 9c2b3a70 René Nussbaumer
      MakeDirWithPerm(dir_path, mkdir_mode, dir_uid, dir_gid)
334 3865ca48 Michael Hanselmann
335 3865ca48 Michael Hanselmann
      return os.rename(old, new)
336 3865ca48 Michael Hanselmann
337 3865ca48 Michael Hanselmann
    raise
338 3865ca48 Michael Hanselmann
339 3865ca48 Michael Hanselmann
340 9c2b3a70 René Nussbaumer
def EnforcePermission(path, mode, uid=None, gid=None, must_exist=True,
341 b81b3c96 René Nussbaumer
                      _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
342 b81b3c96 René Nussbaumer
  """Enforces that given path has given permissions.
343 b81b3c96 René Nussbaumer

344 b81b3c96 René Nussbaumer
  @param path: The path to the file
345 b81b3c96 René Nussbaumer
  @param mode: The mode of the file
346 b81b3c96 René Nussbaumer
  @param uid: The uid of the owner of this file
347 b81b3c96 René Nussbaumer
  @param gid: The gid of the owner of this file
348 b81b3c96 René Nussbaumer
  @param must_exist: Specifies if non-existance of path will be an error
349 b81b3c96 René Nussbaumer
  @param _chmod_fn: chmod function to use (unittest only)
350 b81b3c96 René Nussbaumer
  @param _chown_fn: chown function to use (unittest only)
351 b81b3c96 René Nussbaumer

352 b81b3c96 René Nussbaumer
  """
353 b81b3c96 René Nussbaumer
  logging.debug("Checking %s", path)
354 9c2b3a70 René Nussbaumer
355 9c2b3a70 René Nussbaumer
  # chown takes -1 if you want to keep one part of the ownership, however
356 9c2b3a70 René Nussbaumer
  # None is Python standard for that. So we remap them here.
357 9c2b3a70 René Nussbaumer
  if uid is None:
358 9c2b3a70 René Nussbaumer
    uid = -1
359 9c2b3a70 René Nussbaumer
  if gid is None:
360 9c2b3a70 René Nussbaumer
    gid = -1
361 9c2b3a70 René Nussbaumer
362 b81b3c96 René Nussbaumer
  try:
363 b81b3c96 René Nussbaumer
    st = _stat_fn(path)
364 b81b3c96 René Nussbaumer
365 b81b3c96 René Nussbaumer
    fmode = stat.S_IMODE(st[stat.ST_MODE])
366 b81b3c96 René Nussbaumer
    if fmode != mode:
367 b81b3c96 René Nussbaumer
      logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
368 b81b3c96 René Nussbaumer
      _chmod_fn(path, mode)
369 b81b3c96 René Nussbaumer
370 b81b3c96 René Nussbaumer
    if max(uid, gid) > -1:
371 b81b3c96 René Nussbaumer
      fuid = st[stat.ST_UID]
372 b81b3c96 René Nussbaumer
      fgid = st[stat.ST_GID]
373 b81b3c96 René Nussbaumer
      if fuid != uid or fgid != gid:
374 b81b3c96 René Nussbaumer
        logging.debug("Changing owner of %s from UID %s/GID %s to"
375 b81b3c96 René Nussbaumer
                      " UID %s/GID %s", path, fuid, fgid, uid, gid)
376 b81b3c96 René Nussbaumer
        _chown_fn(path, uid, gid)
377 b81b3c96 René Nussbaumer
  except EnvironmentError, err:
378 b81b3c96 René Nussbaumer
    if err.errno == errno.ENOENT:
379 b81b3c96 René Nussbaumer
      if must_exist:
380 b81b3c96 René Nussbaumer
        raise errors.GenericError("Path %s should exist, but does not" % path)
381 b81b3c96 René Nussbaumer
    else:
382 b81b3c96 René Nussbaumer
      raise errors.GenericError("Error while changing permissions on %s: %s" %
383 b81b3c96 René Nussbaumer
                                (path, err))
384 b81b3c96 René Nussbaumer
385 b81b3c96 René Nussbaumer
386 b81b3c96 René Nussbaumer
def MakeDirWithPerm(path, mode, uid, gid, _lstat_fn=os.lstat,
387 b81b3c96 René Nussbaumer
                    _mkdir_fn=os.mkdir, _perm_fn=EnforcePermission):
388 b81b3c96 René Nussbaumer
  """Enforces that given path is a dir and has given mode, uid and gid set.
389 b81b3c96 René Nussbaumer

390 b81b3c96 René Nussbaumer
  @param path: The path to the file
391 b81b3c96 René Nussbaumer
  @param mode: The mode of the file
392 b81b3c96 René Nussbaumer
  @param uid: The uid of the owner of this file
393 b81b3c96 René Nussbaumer
  @param gid: The gid of the owner of this file
394 b81b3c96 René Nussbaumer
  @param _lstat_fn: Stat function to use (unittest only)
395 b81b3c96 René Nussbaumer
  @param _mkdir_fn: mkdir function to use (unittest only)
396 b81b3c96 René Nussbaumer
  @param _perm_fn: permission setter function to use (unittest only)
397 b81b3c96 René Nussbaumer

398 b81b3c96 René Nussbaumer
  """
399 b81b3c96 René Nussbaumer
  logging.debug("Checking directory %s", path)
400 b81b3c96 René Nussbaumer
  try:
401 b81b3c96 René Nussbaumer
    # We don't want to follow symlinks
402 b81b3c96 René Nussbaumer
    st = _lstat_fn(path)
403 b81b3c96 René Nussbaumer
  except EnvironmentError, err:
404 b81b3c96 René Nussbaumer
    if err.errno != errno.ENOENT:
405 b81b3c96 René Nussbaumer
      raise errors.GenericError("stat(2) on %s failed: %s" % (path, err))
406 b81b3c96 René Nussbaumer
    _mkdir_fn(path)
407 b81b3c96 René Nussbaumer
  else:
408 b81b3c96 René Nussbaumer
    if not stat.S_ISDIR(st[stat.ST_MODE]):
409 b81b3c96 René Nussbaumer
      raise errors.GenericError(("Path %s is expected to be a directory, but "
410 b81b3c96 René Nussbaumer
                                 "isn't") % path)
411 b81b3c96 René Nussbaumer
412 b81b3c96 René Nussbaumer
  _perm_fn(path, mode, uid=uid, gid=gid)
413 b81b3c96 René Nussbaumer
414 b81b3c96 René Nussbaumer
415 3865ca48 Michael Hanselmann
def Makedirs(path, mode=0750):
416 3865ca48 Michael Hanselmann
  """Super-mkdir; create a leaf directory and all intermediate ones.
417 3865ca48 Michael Hanselmann

418 3865ca48 Michael Hanselmann
  This is a wrapper around C{os.makedirs} adding error handling not implemented
419 3865ca48 Michael Hanselmann
  before Python 2.5.
420 3865ca48 Michael Hanselmann

421 3865ca48 Michael Hanselmann
  """
422 3865ca48 Michael Hanselmann
  try:
423 3865ca48 Michael Hanselmann
    os.makedirs(path, mode)
424 3865ca48 Michael Hanselmann
  except OSError, err:
425 3865ca48 Michael Hanselmann
    # Ignore EEXIST. This is only handled in os.makedirs as included in
426 3865ca48 Michael Hanselmann
    # Python 2.5 and above.
427 3865ca48 Michael Hanselmann
    if err.errno != errno.EEXIST or not os.path.exists(path):
428 3865ca48 Michael Hanselmann
      raise
429 3865ca48 Michael Hanselmann
430 3865ca48 Michael Hanselmann
431 3865ca48 Michael Hanselmann
def TimestampForFilename():
432 3865ca48 Michael Hanselmann
  """Returns the current time formatted for filenames.
433 3865ca48 Michael Hanselmann

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

437 3865ca48 Michael Hanselmann
  """
438 3865ca48 Michael Hanselmann
  return time.strftime("%Y-%m-%d_%H_%M_%S")
439 3865ca48 Michael Hanselmann
440 3865ca48 Michael Hanselmann
441 3865ca48 Michael Hanselmann
def CreateBackup(file_name):
442 3865ca48 Michael Hanselmann
  """Creates a backup of a file.
443 3865ca48 Michael Hanselmann

444 3865ca48 Michael Hanselmann
  @type file_name: str
445 3865ca48 Michael Hanselmann
  @param file_name: file to be backed up
446 3865ca48 Michael Hanselmann
  @rtype: str
447 3865ca48 Michael Hanselmann
  @return: the path to the newly created backup
448 3865ca48 Michael Hanselmann
  @raise errors.ProgrammerError: for invalid file names
449 3865ca48 Michael Hanselmann

450 3865ca48 Michael Hanselmann
  """
451 3865ca48 Michael Hanselmann
  if not os.path.isfile(file_name):
452 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
453 3865ca48 Michael Hanselmann
                                file_name)
454 3865ca48 Michael Hanselmann
455 3865ca48 Michael Hanselmann
  prefix = ("%s.backup-%s." %
456 3865ca48 Michael Hanselmann
            (os.path.basename(file_name), TimestampForFilename()))
457 3865ca48 Michael Hanselmann
  dir_name = os.path.dirname(file_name)
458 3865ca48 Michael Hanselmann
459 d0c8c01d Iustin Pop
  fsrc = open(file_name, "rb")
460 3865ca48 Michael Hanselmann
  try:
461 3865ca48 Michael Hanselmann
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
462 d0c8c01d Iustin Pop
    fdst = os.fdopen(fd, "wb")
463 3865ca48 Michael Hanselmann
    try:
464 3865ca48 Michael Hanselmann
      logging.debug("Backing up %s at %s", file_name, backup_name)
465 3865ca48 Michael Hanselmann
      shutil.copyfileobj(fsrc, fdst)
466 3865ca48 Michael Hanselmann
    finally:
467 3865ca48 Michael Hanselmann
      fdst.close()
468 3865ca48 Michael Hanselmann
  finally:
469 3865ca48 Michael Hanselmann
    fsrc.close()
470 3865ca48 Michael Hanselmann
471 3865ca48 Michael Hanselmann
  return backup_name
472 3865ca48 Michael Hanselmann
473 3865ca48 Michael Hanselmann
474 2dbc6857 Michael Hanselmann
def ListVisibleFiles(path, _is_mountpoint=os.path.ismount):
475 3865ca48 Michael Hanselmann
  """Returns a list of visible files in a directory.
476 3865ca48 Michael Hanselmann

477 3865ca48 Michael Hanselmann
  @type path: str
478 3865ca48 Michael Hanselmann
  @param path: the directory to enumerate
479 3865ca48 Michael Hanselmann
  @rtype: list
480 3865ca48 Michael Hanselmann
  @return: the list of all files not starting with a dot
481 3865ca48 Michael Hanselmann
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
482 3865ca48 Michael Hanselmann

483 3865ca48 Michael Hanselmann
  """
484 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(path):
485 3865ca48 Michael Hanselmann
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
486 3865ca48 Michael Hanselmann
                                 " absolute/normalized: '%s'" % path)
487 2dbc6857 Michael Hanselmann
488 2dbc6857 Michael Hanselmann
  mountpoint = _is_mountpoint(path)
489 2dbc6857 Michael Hanselmann
490 2dbc6857 Michael Hanselmann
  def fn(name):
491 2dbc6857 Michael Hanselmann
    """File name filter.
492 2dbc6857 Michael Hanselmann

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

497 2dbc6857 Michael Hanselmann
    """
498 2dbc6857 Michael Hanselmann
    return not (name.startswith(".") or
499 2dbc6857 Michael Hanselmann
                (mountpoint and name == _LOST_AND_FOUND and
500 2dbc6857 Michael Hanselmann
                 os.path.isdir(os.path.join(path, name))))
501 2dbc6857 Michael Hanselmann
502 2dbc6857 Michael Hanselmann
  return filter(fn, os.listdir(path))
503 3865ca48 Michael Hanselmann
504 3865ca48 Michael Hanselmann
505 3865ca48 Michael Hanselmann
def EnsureDirs(dirs):
506 3865ca48 Michael Hanselmann
  """Make required directories, if they don't exist.
507 3865ca48 Michael Hanselmann

508 3865ca48 Michael Hanselmann
  @param dirs: list of tuples (dir_name, dir_mode)
509 3865ca48 Michael Hanselmann
  @type dirs: list of (string, integer)
510 3865ca48 Michael Hanselmann

511 3865ca48 Michael Hanselmann
  """
512 3865ca48 Michael Hanselmann
  for dir_name, dir_mode in dirs:
513 3865ca48 Michael Hanselmann
    try:
514 3865ca48 Michael Hanselmann
      os.mkdir(dir_name, dir_mode)
515 3865ca48 Michael Hanselmann
    except EnvironmentError, err:
516 3865ca48 Michael Hanselmann
      if err.errno != errno.EEXIST:
517 3865ca48 Michael Hanselmann
        raise errors.GenericError("Cannot create needed directory"
518 3865ca48 Michael Hanselmann
                                  " '%s': %s" % (dir_name, err))
519 3865ca48 Michael Hanselmann
    try:
520 3865ca48 Michael Hanselmann
      os.chmod(dir_name, dir_mode)
521 3865ca48 Michael Hanselmann
    except EnvironmentError, err:
522 3865ca48 Michael Hanselmann
      raise errors.GenericError("Cannot change directory permissions on"
523 3865ca48 Michael Hanselmann
                                " '%s': %s" % (dir_name, err))
524 3865ca48 Michael Hanselmann
    if not os.path.isdir(dir_name):
525 3865ca48 Michael Hanselmann
      raise errors.GenericError("%s is not a directory" % dir_name)
526 3865ca48 Michael Hanselmann
527 3865ca48 Michael Hanselmann
528 3865ca48 Michael Hanselmann
def FindFile(name, search_path, test=os.path.exists):
529 3865ca48 Michael Hanselmann
  """Look for a filesystem object in a given path.
530 3865ca48 Michael Hanselmann

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

534 3865ca48 Michael Hanselmann
  @type name: str
535 3865ca48 Michael Hanselmann
  @param name: the name to look for
536 3865ca48 Michael Hanselmann
  @type search_path: str
537 3865ca48 Michael Hanselmann
  @param search_path: location to start at
538 3865ca48 Michael Hanselmann
  @type test: callable
539 3865ca48 Michael Hanselmann
  @param test: a function taking one argument that should return True
540 3865ca48 Michael Hanselmann
      if the a given object is valid; the default value is
541 3865ca48 Michael Hanselmann
      os.path.exists, causing only existing files to be returned
542 3865ca48 Michael Hanselmann
  @rtype: str or None
543 3865ca48 Michael Hanselmann
  @return: full path to the object if found, None otherwise
544 3865ca48 Michael Hanselmann

545 3865ca48 Michael Hanselmann
  """
546 3865ca48 Michael Hanselmann
  # validate the filename mask
547 3865ca48 Michael Hanselmann
  if constants.EXT_PLUGIN_MASK.match(name) is None:
548 3865ca48 Michael Hanselmann
    logging.critical("Invalid value passed for external script name: '%s'",
549 3865ca48 Michael Hanselmann
                     name)
550 3865ca48 Michael Hanselmann
    return None
551 3865ca48 Michael Hanselmann
552 3865ca48 Michael Hanselmann
  for dir_name in search_path:
553 3865ca48 Michael Hanselmann
    # FIXME: investigate switch to PathJoin
554 3865ca48 Michael Hanselmann
    item_name = os.path.sep.join([dir_name, name])
555 3865ca48 Michael Hanselmann
    # check the user test and that we're indeed resolving to the given
556 3865ca48 Michael Hanselmann
    # basename
557 3865ca48 Michael Hanselmann
    if test(item_name) and os.path.basename(item_name) == name:
558 3865ca48 Michael Hanselmann
      return item_name
559 3865ca48 Michael Hanselmann
  return None
560 3865ca48 Michael Hanselmann
561 3865ca48 Michael Hanselmann
562 3865ca48 Michael Hanselmann
def IsNormAbsPath(path):
563 3865ca48 Michael Hanselmann
  """Check whether a path is absolute and also normalized
564 3865ca48 Michael Hanselmann

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

567 3865ca48 Michael Hanselmann
  """
568 3865ca48 Michael Hanselmann
  return os.path.normpath(path) == path and os.path.isabs(path)
569 3865ca48 Michael Hanselmann
570 3865ca48 Michael Hanselmann
571 fb17bebd René Nussbaumer
def IsBelowDir(root, other_path):
572 fb17bebd René Nussbaumer
  """Check whether a path is below a root dir.
573 fb17bebd René Nussbaumer

574 fb17bebd René Nussbaumer
  This works around the nasty byte-byte comparisation of commonprefix.
575 fb17bebd René Nussbaumer

576 fb17bebd René Nussbaumer
  """
577 fb17bebd René Nussbaumer
  if not (os.path.isabs(root) and os.path.isabs(other_path)):
578 fb17bebd René Nussbaumer
    raise ValueError("Provided paths '%s' and '%s' are not absolute" %
579 fb17bebd René Nussbaumer
                     (root, other_path))
580 fb17bebd René Nussbaumer
  prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
581 fb17bebd René Nussbaumer
  return os.path.commonprefix([prepared_root,
582 fb17bebd René Nussbaumer
                               os.path.normpath(other_path)]) == prepared_root
583 fb17bebd René Nussbaumer
584 fb17bebd René Nussbaumer
585 3865ca48 Michael Hanselmann
def PathJoin(*args):
586 3865ca48 Michael Hanselmann
  """Safe-join a list of path components.
587 3865ca48 Michael Hanselmann

588 3865ca48 Michael Hanselmann
  Requirements:
589 3865ca48 Michael Hanselmann
      - the first argument must be an absolute path
590 3865ca48 Michael Hanselmann
      - no component in the path must have backtracking (e.g. /../),
591 3865ca48 Michael Hanselmann
        since we check for normalization at the end
592 3865ca48 Michael Hanselmann

593 3865ca48 Michael Hanselmann
  @param args: the path components to be joined
594 3865ca48 Michael Hanselmann
  @raise ValueError: for invalid paths
595 3865ca48 Michael Hanselmann

596 3865ca48 Michael Hanselmann
  """
597 3865ca48 Michael Hanselmann
  # ensure we're having at least one path passed in
598 3865ca48 Michael Hanselmann
  assert args
599 3865ca48 Michael Hanselmann
  # ensure the first component is an absolute and normalized path name
600 3865ca48 Michael Hanselmann
  root = args[0]
601 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(root):
602 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
603 3865ca48 Michael Hanselmann
  result = os.path.join(*args)
604 3865ca48 Michael Hanselmann
  # ensure that the whole path is normalized
605 3865ca48 Michael Hanselmann
  if not IsNormAbsPath(result):
606 3865ca48 Michael Hanselmann
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
607 3865ca48 Michael Hanselmann
  # check that we're still under the original prefix
608 cf00dba0 René Nussbaumer
  if not IsBelowDir(root, result):
609 3865ca48 Michael Hanselmann
    raise ValueError("Error: path joining resulted in different prefix"
610 cf00dba0 René Nussbaumer
                     " (%s != %s)" % (result, root))
611 3865ca48 Michael Hanselmann
  return result
612 3865ca48 Michael Hanselmann
613 3865ca48 Michael Hanselmann
614 3865ca48 Michael Hanselmann
def TailFile(fname, lines=20):
615 3865ca48 Michael Hanselmann
  """Return the last lines from a file.
616 3865ca48 Michael Hanselmann

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

621 3865ca48 Michael Hanselmann
  @param fname: the file name
622 3865ca48 Michael Hanselmann
  @type lines: int
623 3865ca48 Michael Hanselmann
  @param lines: the (maximum) number of lines to return
624 3865ca48 Michael Hanselmann

625 3865ca48 Michael Hanselmann
  """
626 3865ca48 Michael Hanselmann
  fd = open(fname, "r")
627 3865ca48 Michael Hanselmann
  try:
628 3865ca48 Michael Hanselmann
    fd.seek(0, 2)
629 3865ca48 Michael Hanselmann
    pos = fd.tell()
630 e687ec01 Michael Hanselmann
    pos = max(0, pos - 4096)
631 3865ca48 Michael Hanselmann
    fd.seek(pos, 0)
632 3865ca48 Michael Hanselmann
    raw_data = fd.read()
633 3865ca48 Michael Hanselmann
  finally:
634 3865ca48 Michael Hanselmann
    fd.close()
635 3865ca48 Michael Hanselmann
636 3865ca48 Michael Hanselmann
  rows = raw_data.splitlines()
637 3865ca48 Michael Hanselmann
  return rows[-lines:]
638 3865ca48 Michael Hanselmann
639 3865ca48 Michael Hanselmann
640 3865ca48 Michael Hanselmann
def BytesToMebibyte(value):
641 3865ca48 Michael Hanselmann
  """Converts bytes to mebibytes.
642 3865ca48 Michael Hanselmann

643 3865ca48 Michael Hanselmann
  @type value: int
644 3865ca48 Michael Hanselmann
  @param value: Value in bytes
645 3865ca48 Michael Hanselmann
  @rtype: int
646 3865ca48 Michael Hanselmann
  @return: Value in mebibytes
647 3865ca48 Michael Hanselmann

648 3865ca48 Michael Hanselmann
  """
649 3865ca48 Michael Hanselmann
  return int(round(value / (1024.0 * 1024.0), 0))
650 3865ca48 Michael Hanselmann
651 3865ca48 Michael Hanselmann
652 3865ca48 Michael Hanselmann
def CalculateDirectorySize(path):
653 3865ca48 Michael Hanselmann
  """Calculates the size of a directory recursively.
654 3865ca48 Michael Hanselmann

655 3865ca48 Michael Hanselmann
  @type path: string
656 3865ca48 Michael Hanselmann
  @param path: Path to directory
657 3865ca48 Michael Hanselmann
  @rtype: int
658 3865ca48 Michael Hanselmann
  @return: Size in mebibytes
659 3865ca48 Michael Hanselmann

660 3865ca48 Michael Hanselmann
  """
661 3865ca48 Michael Hanselmann
  size = 0
662 3865ca48 Michael Hanselmann
663 3865ca48 Michael Hanselmann
  for (curpath, _, files) in os.walk(path):
664 3865ca48 Michael Hanselmann
    for filename in files:
665 3865ca48 Michael Hanselmann
      st = os.lstat(PathJoin(curpath, filename))
666 3865ca48 Michael Hanselmann
      size += st.st_size
667 3865ca48 Michael Hanselmann
668 3865ca48 Michael Hanselmann
  return BytesToMebibyte(size)
669 3865ca48 Michael Hanselmann
670 3865ca48 Michael Hanselmann
671 3865ca48 Michael Hanselmann
def GetFilesystemStats(path):
672 3865ca48 Michael Hanselmann
  """Returns the total and free space on a filesystem.
673 3865ca48 Michael Hanselmann

674 3865ca48 Michael Hanselmann
  @type path: string
675 3865ca48 Michael Hanselmann
  @param path: Path on filesystem to be examined
676 3865ca48 Michael Hanselmann
  @rtype: int
677 3865ca48 Michael Hanselmann
  @return: tuple of (Total space, Free space) in mebibytes
678 3865ca48 Michael Hanselmann

679 3865ca48 Michael Hanselmann
  """
680 3865ca48 Michael Hanselmann
  st = os.statvfs(path)
681 3865ca48 Michael Hanselmann
682 3865ca48 Michael Hanselmann
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
683 3865ca48 Michael Hanselmann
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
684 3865ca48 Michael Hanselmann
  return (tsize, fsize)
685 3865ca48 Michael Hanselmann
686 3865ca48 Michael Hanselmann
687 3865ca48 Michael Hanselmann
def ReadPidFile(pidfile):
688 3865ca48 Michael Hanselmann
  """Read a pid from a file.
689 3865ca48 Michael Hanselmann

690 3865ca48 Michael Hanselmann
  @type  pidfile: string
691 3865ca48 Michael Hanselmann
  @param pidfile: path to the file containing the pid
692 3865ca48 Michael Hanselmann
  @rtype: int
693 3865ca48 Michael Hanselmann
  @return: The process id, if the file exists and contains a valid PID,
694 3865ca48 Michael Hanselmann
           otherwise 0
695 3865ca48 Michael Hanselmann

696 3865ca48 Michael Hanselmann
  """
697 3865ca48 Michael Hanselmann
  try:
698 3865ca48 Michael Hanselmann
    raw_data = ReadOneLineFile(pidfile)
699 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
700 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
701 3865ca48 Michael Hanselmann
      logging.exception("Can't read pid file")
702 3865ca48 Michael Hanselmann
    return 0
703 3865ca48 Michael Hanselmann
704 b6522276 Michael Hanselmann
  return _ParsePidFileContents(raw_data)
705 b6522276 Michael Hanselmann
706 b6522276 Michael Hanselmann
707 b6522276 Michael Hanselmann
def _ParsePidFileContents(data):
708 b6522276 Michael Hanselmann
  """Tries to extract a process ID from a PID file's content.
709 b6522276 Michael Hanselmann

710 b6522276 Michael Hanselmann
  @type data: string
711 b6522276 Michael Hanselmann
  @rtype: int
712 b6522276 Michael Hanselmann
  @return: Zero if nothing could be read, PID otherwise
713 b6522276 Michael Hanselmann

714 b6522276 Michael Hanselmann
  """
715 3865ca48 Michael Hanselmann
  try:
716 b6522276 Michael Hanselmann
    pid = int(data)
717 b6522276 Michael Hanselmann
  except (TypeError, ValueError):
718 3865ca48 Michael Hanselmann
    logging.info("Can't parse pid file contents", exc_info=True)
719 3865ca48 Michael Hanselmann
    return 0
720 b6522276 Michael Hanselmann
  else:
721 b6522276 Michael Hanselmann
    return pid
722 3865ca48 Michael Hanselmann
723 3865ca48 Michael Hanselmann
724 3865ca48 Michael Hanselmann
def ReadLockedPidFile(path):
725 3865ca48 Michael Hanselmann
  """Reads a locked PID file.
726 3865ca48 Michael Hanselmann

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

729 3865ca48 Michael Hanselmann
  @type path: string
730 3865ca48 Michael Hanselmann
  @param path: Path to PID file
731 3865ca48 Michael Hanselmann
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
732 3865ca48 Michael Hanselmann

733 3865ca48 Michael Hanselmann
  """
734 3865ca48 Michael Hanselmann
  try:
735 3865ca48 Michael Hanselmann
    fd = os.open(path, os.O_RDONLY)
736 3865ca48 Michael Hanselmann
  except EnvironmentError, err:
737 3865ca48 Michael Hanselmann
    if err.errno == errno.ENOENT:
738 3865ca48 Michael Hanselmann
      # PID file doesn't exist
739 3865ca48 Michael Hanselmann
      return None
740 3865ca48 Michael Hanselmann
    raise
741 3865ca48 Michael Hanselmann
742 3865ca48 Michael Hanselmann
  try:
743 3865ca48 Michael Hanselmann
    try:
744 3865ca48 Michael Hanselmann
      # Try to acquire lock
745 3865ca48 Michael Hanselmann
      filelock.LockFile(fd)
746 3865ca48 Michael Hanselmann
    except errors.LockError:
747 3865ca48 Michael Hanselmann
      # Couldn't lock, daemon is running
748 3865ca48 Michael Hanselmann
      return int(os.read(fd, 100))
749 3865ca48 Michael Hanselmann
  finally:
750 3865ca48 Michael Hanselmann
    os.close(fd)
751 3865ca48 Michael Hanselmann
752 3865ca48 Michael Hanselmann
  return None
753 3865ca48 Michael Hanselmann
754 3865ca48 Michael Hanselmann
755 3865ca48 Michael Hanselmann
def AddAuthorizedKey(file_obj, key):
756 3865ca48 Michael Hanselmann
  """Adds an SSH public key to an authorized_keys file.
757 3865ca48 Michael Hanselmann

758 3865ca48 Michael Hanselmann
  @type file_obj: str or file handle
759 3865ca48 Michael Hanselmann
  @param file_obj: path to authorized_keys file
760 3865ca48 Michael Hanselmann
  @type key: str
761 3865ca48 Michael Hanselmann
  @param key: string containing key
762 3865ca48 Michael Hanselmann

763 3865ca48 Michael Hanselmann
  """
764 3865ca48 Michael Hanselmann
  key_fields = key.split()
765 3865ca48 Michael Hanselmann
766 3865ca48 Michael Hanselmann
  if isinstance(file_obj, basestring):
767 d0c8c01d Iustin Pop
    f = open(file_obj, "a+")
768 3865ca48 Michael Hanselmann
  else:
769 3865ca48 Michael Hanselmann
    f = file_obj
770 3865ca48 Michael Hanselmann
771 3865ca48 Michael Hanselmann
  try:
772 3865ca48 Michael Hanselmann
    nl = True
773 3865ca48 Michael Hanselmann
    for line in f:
774 3865ca48 Michael Hanselmann
      # Ignore whitespace changes
775 3865ca48 Michael Hanselmann
      if line.split() == key_fields:
776 3865ca48 Michael Hanselmann
        break
777 d0c8c01d Iustin Pop
      nl = line.endswith("\n")
778 3865ca48 Michael Hanselmann
    else:
779 3865ca48 Michael Hanselmann
      if not nl:
780 3865ca48 Michael Hanselmann
        f.write("\n")
781 d0c8c01d Iustin Pop
      f.write(key.rstrip("\r\n"))
782 3865ca48 Michael Hanselmann
      f.write("\n")
783 3865ca48 Michael Hanselmann
      f.flush()
784 3865ca48 Michael Hanselmann
  finally:
785 3865ca48 Michael Hanselmann
    f.close()
786 3865ca48 Michael Hanselmann
787 3865ca48 Michael Hanselmann
788 3865ca48 Michael Hanselmann
def RemoveAuthorizedKey(file_name, key):
789 3865ca48 Michael Hanselmann
  """Removes an SSH public key from an authorized_keys file.
790 3865ca48 Michael Hanselmann

791 3865ca48 Michael Hanselmann
  @type file_name: str
792 3865ca48 Michael Hanselmann
  @param file_name: path to authorized_keys file
793 3865ca48 Michael Hanselmann
  @type key: str
794 3865ca48 Michael Hanselmann
  @param key: string containing key
795 3865ca48 Michael Hanselmann

796 3865ca48 Michael Hanselmann
  """
797 3865ca48 Michael Hanselmann
  key_fields = key.split()
798 3865ca48 Michael Hanselmann
799 3865ca48 Michael Hanselmann
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
800 3865ca48 Michael Hanselmann
  try:
801 d0c8c01d Iustin Pop
    out = os.fdopen(fd, "w")
802 3865ca48 Michael Hanselmann
    try:
803 d0c8c01d Iustin Pop
      f = open(file_name, "r")
804 3865ca48 Michael Hanselmann
      try:
805 3865ca48 Michael Hanselmann
        for line in f:
806 3865ca48 Michael Hanselmann
          # Ignore whitespace changes while comparing lines
807 3865ca48 Michael Hanselmann
          if line.split() != key_fields:
808 3865ca48 Michael Hanselmann
            out.write(line)
809 3865ca48 Michael Hanselmann
810 3865ca48 Michael Hanselmann
        out.flush()
811 3865ca48 Michael Hanselmann
        os.rename(tmpname, file_name)
812 3865ca48 Michael Hanselmann
      finally:
813 3865ca48 Michael Hanselmann
        f.close()
814 3865ca48 Michael Hanselmann
    finally:
815 3865ca48 Michael Hanselmann
      out.close()
816 3865ca48 Michael Hanselmann
  except:
817 3865ca48 Michael Hanselmann
    RemoveFile(tmpname)
818 3865ca48 Michael Hanselmann
    raise
819 3865ca48 Michael Hanselmann
820 3865ca48 Michael Hanselmann
821 3865ca48 Michael Hanselmann
def DaemonPidFileName(name):
822 3865ca48 Michael Hanselmann
  """Compute a ganeti pid file absolute path
823 3865ca48 Michael Hanselmann

824 3865ca48 Michael Hanselmann
  @type name: str
825 3865ca48 Michael Hanselmann
  @param name: the daemon name
826 3865ca48 Michael Hanselmann
  @rtype: str
827 3865ca48 Michael Hanselmann
  @return: the full path to the pidfile corresponding to the given
828 3865ca48 Michael Hanselmann
      daemon name
829 3865ca48 Michael Hanselmann

830 3865ca48 Michael Hanselmann
  """
831 3865ca48 Michael Hanselmann
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
832 3865ca48 Michael Hanselmann
833 3865ca48 Michael Hanselmann
834 3865ca48 Michael Hanselmann
def WritePidFile(pidfile):
835 3865ca48 Michael Hanselmann
  """Write the current process pidfile.
836 3865ca48 Michael Hanselmann

837 3865ca48 Michael Hanselmann
  @type pidfile: string
838 3865ca48 Michael Hanselmann
  @param pidfile: the path to the file to be written
839 3865ca48 Michael Hanselmann
  @raise errors.LockError: if the pid file already exists and
840 3865ca48 Michael Hanselmann
      points to a live process
841 3865ca48 Michael Hanselmann
  @rtype: int
842 3865ca48 Michael Hanselmann
  @return: the file descriptor of the lock file; do not close this unless
843 3865ca48 Michael Hanselmann
      you want to unlock the pid file
844 3865ca48 Michael Hanselmann

845 3865ca48 Michael Hanselmann
  """
846 3865ca48 Michael Hanselmann
  # We don't rename nor truncate the file to not drop locks under
847 3865ca48 Michael Hanselmann
  # existing processes
848 b6522276 Michael Hanselmann
  fd_pidfile = os.open(pidfile, os.O_RDWR | os.O_CREAT, 0600)
849 3865ca48 Michael Hanselmann
850 3865ca48 Michael Hanselmann
  # Lock the PID file (and fail if not possible to do so). Any code
851 3865ca48 Michael Hanselmann
  # wanting to send a signal to the daemon should try to lock the PID
852 3865ca48 Michael Hanselmann
  # file before reading it. If acquiring the lock succeeds, the daemon is
853 3865ca48 Michael Hanselmann
  # no longer running and the signal should not be sent.
854 b6522276 Michael Hanselmann
  try:
855 b6522276 Michael Hanselmann
    filelock.LockFile(fd_pidfile)
856 b6522276 Michael Hanselmann
  except errors.LockError:
857 b6522276 Michael Hanselmann
    msg = ["PID file '%s' is already locked by another process" % pidfile]
858 b6522276 Michael Hanselmann
    # Try to read PID file
859 b6522276 Michael Hanselmann
    pid = _ParsePidFileContents(os.read(fd_pidfile, 100))
860 b6522276 Michael Hanselmann
    if pid > 0:
861 b6522276 Michael Hanselmann
      msg.append(", PID read from file is %s" % pid)
862 b6522276 Michael Hanselmann
    raise errors.PidFileLockError("".join(msg))
863 3865ca48 Michael Hanselmann
864 3865ca48 Michael Hanselmann
  os.write(fd_pidfile, "%d\n" % os.getpid())
865 3865ca48 Michael Hanselmann
866 3865ca48 Michael Hanselmann
  return fd_pidfile
867 3865ca48 Michael Hanselmann
868 3865ca48 Michael Hanselmann
869 3865ca48 Michael Hanselmann
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
870 3865ca48 Michael Hanselmann
  """Reads the watcher pause file.
871 3865ca48 Michael Hanselmann

872 3865ca48 Michael Hanselmann
  @type filename: string
873 3865ca48 Michael Hanselmann
  @param filename: Path to watcher pause file
874 3865ca48 Michael Hanselmann
  @type now: None, float or int
875 3865ca48 Michael Hanselmann
  @param now: Current time as Unix timestamp
876 3865ca48 Michael Hanselmann
  @type remove_after: int
877 3865ca48 Michael Hanselmann
  @param remove_after: Remove watcher pause file after specified amount of
878 3865ca48 Michael Hanselmann
    seconds past the pause end time
879 3865ca48 Michael Hanselmann

880 3865ca48 Michael Hanselmann
  """
881 3865ca48 Michael Hanselmann
  if now is None:
882 3865ca48 Michael Hanselmann
    now = time.time()
883 3865ca48 Michael Hanselmann
884 3865ca48 Michael Hanselmann
  try:
885 3865ca48 Michael Hanselmann
    value = ReadFile(filename)
886 3865ca48 Michael Hanselmann
  except IOError, err:
887 3865ca48 Michael Hanselmann
    if err.errno != errno.ENOENT:
888 3865ca48 Michael Hanselmann
      raise
889 3865ca48 Michael Hanselmann
    value = None
890 3865ca48 Michael Hanselmann
891 3865ca48 Michael Hanselmann
  if value is not None:
892 3865ca48 Michael Hanselmann
    try:
893 3865ca48 Michael Hanselmann
      value = int(value)
894 3865ca48 Michael Hanselmann
    except ValueError:
895 3865ca48 Michael Hanselmann
      logging.warning(("Watcher pause file (%s) contains invalid value,"
896 3865ca48 Michael Hanselmann
                       " removing it"), filename)
897 3865ca48 Michael Hanselmann
      RemoveFile(filename)
898 3865ca48 Michael Hanselmann
      value = None
899 3865ca48 Michael Hanselmann
900 3865ca48 Michael Hanselmann
    if value is not None:
901 3865ca48 Michael Hanselmann
      # Remove file if it's outdated
902 3865ca48 Michael Hanselmann
      if now > (value + remove_after):
903 3865ca48 Michael Hanselmann
        RemoveFile(filename)
904 3865ca48 Michael Hanselmann
        value = None
905 3865ca48 Michael Hanselmann
906 3865ca48 Michael Hanselmann
      elif now > value:
907 3865ca48 Michael Hanselmann
        value = None
908 3865ca48 Michael Hanselmann
909 3865ca48 Michael Hanselmann
  return value
910 90e234a6 Michael Hanselmann
911 90e234a6 Michael Hanselmann
912 90e234a6 Michael Hanselmann
def NewUUID():
913 90e234a6 Michael Hanselmann
  """Returns a random UUID.
914 90e234a6 Michael Hanselmann

915 90e234a6 Michael Hanselmann
  @note: This is a Linux-specific method as it uses the /proc
916 90e234a6 Michael Hanselmann
      filesystem.
917 90e234a6 Michael Hanselmann
  @rtype: str
918 90e234a6 Michael Hanselmann

919 90e234a6 Michael Hanselmann
  """
920 90e234a6 Michael Hanselmann
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")