import tempfile
import errno
import time
+import stat
from ganeti import errors
from ganeti import constants
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
-def ReadFile(file_name, size=-1):
+def ErrnoOrStr(err):
+ """Format an EnvironmentError exception.
+
+ If the L{err} argument has an errno attribute, it will be looked up
+ and converted into a textual C{E...} description. Otherwise the
+ string representation of the error will be returned.
+
+ @type err: L{EnvironmentError}
+ @param err: the exception to format
+
+ """
+ if hasattr(err, "errno"):
+ detail = errno.errorcode[err.errno]
+ else:
+ detail = str(err)
+ return detail
+
+
+def ReadFile(file_name, size=-1, preread=None):
"""Reads a file.
@type size: int
@param size: Read at most size bytes (if negative, entire file)
+ @type preread: callable receiving file handle as single parameter
+ @param preread: Function called before file is read
@rtype: str
@return: the (possibly partial) content of the file
"""
f = open(file_name, "r")
try:
+ if preread:
+ preread(f)
+
return f.read(size)
finally:
f.close()
dir_gid=None):
"""Renames a file.
+ This just creates the very least directory if it does not exist and C{mkdir}
+ is set to true.
+
@type old: string
@param old: Original path
@type new: string
if mkdir and err.errno == errno.ENOENT:
# Create directory and try again
dir_path = os.path.dirname(new)
- Makedirs(dir_path, mode=mkdir_mode)
- if not (dir_uid is None or dir_gid is None):
- os.chown(dir_path, dir_uid, dir_gid)
+ MakeDirWithPerm(dir_path, mkdir_mode, dir_uid, dir_gid)
return os.rename(old, new)
raise
+def EnforcePermission(path, mode, uid=None, gid=None, must_exist=True,
+ _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
+ """Enforces that given path has given permissions.
+
+ @param path: The path to the file
+ @param mode: The mode of the file
+ @param uid: The uid of the owner of this file
+ @param gid: The gid of the owner of this file
+ @param must_exist: Specifies if non-existance of path will be an error
+ @param _chmod_fn: chmod function to use (unittest only)
+ @param _chown_fn: chown function to use (unittest only)
+
+ """
+ logging.debug("Checking %s", path)
+
+ # chown takes -1 if you want to keep one part of the ownership, however
+ # None is Python standard for that. So we remap them here.
+ if uid is None:
+ uid = -1
+ if gid is None:
+ gid = -1
+
+ try:
+ st = _stat_fn(path)
+
+ fmode = stat.S_IMODE(st[stat.ST_MODE])
+ if fmode != mode:
+ logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
+ _chmod_fn(path, mode)
+
+ if max(uid, gid) > -1:
+ fuid = st[stat.ST_UID]
+ fgid = st[stat.ST_GID]
+ if fuid != uid or fgid != gid:
+ logging.debug("Changing owner of %s from UID %s/GID %s to"
+ " UID %s/GID %s", path, fuid, fgid, uid, gid)
+ _chown_fn(path, uid, gid)
+ except EnvironmentError, err:
+ if err.errno == errno.ENOENT:
+ if must_exist:
+ raise errors.GenericError("Path %s should exist, but does not" % path)
+ else:
+ raise errors.GenericError("Error while changing permissions on %s: %s" %
+ (path, err))
+
+
+def MakeDirWithPerm(path, mode, uid, gid, _lstat_fn=os.lstat,
+ _mkdir_fn=os.mkdir, _perm_fn=EnforcePermission):
+ """Enforces that given path is a dir and has given mode, uid and gid set.
+
+ @param path: The path to the file
+ @param mode: The mode of the file
+ @param uid: The uid of the owner of this file
+ @param gid: The gid of the owner of this file
+ @param _lstat_fn: Stat function to use (unittest only)
+ @param _mkdir_fn: mkdir function to use (unittest only)
+ @param _perm_fn: permission setter function to use (unittest only)
+
+ """
+ logging.debug("Checking directory %s", path)
+ try:
+ # We don't want to follow symlinks
+ st = _lstat_fn(path)
+ except EnvironmentError, err:
+ if err.errno != errno.ENOENT:
+ raise errors.GenericError("stat(2) on %s failed: %s" % (path, err))
+ _mkdir_fn(path)
+ else:
+ if not stat.S_ISDIR(st[stat.ST_MODE]):
+ raise errors.GenericError(("Path %s is expected to be a directory, but "
+ "isn't") % path)
+
+ _perm_fn(path, mode, uid=uid, gid=gid)
+
+
def Makedirs(path, mode=0750):
"""Super-mkdir; create a leaf directory and all intermediate ones.
(os.path.basename(file_name), TimestampForFilename()))
dir_name = os.path.dirname(file_name)
- fsrc = open(file_name, 'rb')
+ fsrc = open(file_name, "rb")
try:
(fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
- fdst = os.fdopen(fd, 'wb')
+ fdst = os.fdopen(fd, "wb")
try:
logging.debug("Backing up %s at %s", file_name, backup_name)
shutil.copyfileobj(fsrc, fdst)
return os.path.normpath(path) == path and os.path.isabs(path)
+def IsBelowDir(root, other_path):
+ """Check whether a path is below a root dir.
+
+ This works around the nasty byte-byte comparisation of commonprefix.
+
+ """
+ if not (os.path.isabs(root) and os.path.isabs(other_path)):
+ raise ValueError("Provided paths '%s' and '%s' are not absolute" %
+ (root, other_path))
+ prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
+ return os.path.commonprefix([prepared_root,
+ os.path.normpath(other_path)]) == prepared_root
+
+
def PathJoin(*args):
"""Safe-join a list of path components.
if not IsNormAbsPath(result):
raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
# check that we're still under the original prefix
- prefix = os.path.commonprefix([root, result])
- if prefix != root:
+ if not IsBelowDir(root, result):
raise ValueError("Error: path joining resulted in different prefix"
- " (%s != %s)" % (prefix, root))
+ " (%s != %s)" % (result, root))
return result
try:
fd.seek(0, 2)
pos = fd.tell()
- pos = max(0, pos-4096)
+ pos = max(0, pos - 4096)
fd.seek(pos, 0)
raw_data = fd.read()
finally:
key_fields = key.split()
if isinstance(file_obj, basestring):
- f = open(file_obj, 'a+')
+ f = open(file_obj, "a+")
else:
f = file_obj
# Ignore whitespace changes
if line.split() == key_fields:
break
- nl = line.endswith('\n')
+ nl = line.endswith("\n")
else:
if not nl:
f.write("\n")
- f.write(key.rstrip('\r\n'))
+ f.write(key.rstrip("\r\n"))
f.write("\n")
f.flush()
finally:
fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
try:
- out = os.fdopen(fd, 'w')
+ out = os.fdopen(fd, "w")
try:
- f = open(file_name, 'r')
+ f = open(file_name, "r")
try:
for line in f:
# Ignore whitespace changes while comparing lines
"""
return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
+
+
+class TemporaryFileManager(object):
+ """Stores the list of files to be deleted and removes them on demand.
+
+ """
+
+ def __init__(self):
+ self._files = []
+
+ def __del__(self):
+ self.Cleanup()
+
+ def Add(self, filename):
+ """Add file to list of files to be deleted.
+
+ @type filename: string
+ @param filename: path to filename to be added
+
+ """
+ self._files.append(filename)
+
+ def Remove(self, filename):
+ """Remove file from list of files to be deleted.
+
+ @type filename: string
+ @param filename: path to filename to be deleted
+
+ """
+ self._files.remove(filename)
+
+ def Cleanup(self):
+ """Delete all files marked for deletion
+
+ """
+ while self._files:
+ RemoveFile(self._files.pop())