#
# Copyright (c) 2018 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
"""
FileUtils - Provides dmmgmnt file-related capabilities.
$Id: //eng/vdo-releases/magnesium/src/python/vdo/utils/FileUtils.py#1 $
"""
from __future__ import print_function
import errno
import fcntl
import gettext
import grp
import logging
import os
import stat
import tempfile
import time
from .Command import Command
from .Timeout import Timeout, TimeoutError
gettext.install("utils")
########################################################################
class FileBase(object):
"""The FileBase object; provides basic file control.
Class attributes:
log (logging.Logger) - logger for this class
Attributes:
None
"""
log = logging.getLogger('utils.FileBase')
######################################################################
# Public methods
######################################################################
@property
def path(self):
return self.__filePath
######################################################################
# Overridden methods
######################################################################
def __init__(self, filePath, *args, **kwargs):
"""
Arguments:
None
Returns:
Nothing
"""
super(FileBase, self).__init__()
self.__filePath = os.path.realpath(filePath)
self.__fd = kwargs.get("fd", None)
######################################################################
def __enter__(self):
return self
######################################################################
def __exit__(self, exceptionType, exceptionValue, traceback):
# Don't suppress exceptions.
return False
######################################################################
# Protected methods
######################################################################
@property
def _fd(self):
return self.__fd
######################################################################
# pylint: disable=E0102
# pylint: disable=E1101
@_fd.setter
def _fd(self, value):
self.__fd = value
######################################################################
# Private methods
######################################################################
########################################################################
class FileTouch(FileBase):
"""The FileTouch object; touches the file.
Class attributes:
log (logging.Logger) - logger for this class
Attributes:
None
"""
log = logging.getLogger('utils.FileTouch')
######################################################################
# Public methods
######################################################################
######################################################################
# Overridden methods
######################################################################
def __init__(self, filePath, *args, **kwargs):
"""
Arguments:
None
Returns:
Nothing
"""
super(FileTouch, self).__init__(filePath, *args, **kwargs)
######################################################################
def __enter__(self):
"""Make certain the file exists and return ourself."""
super(FileTouch, self).__enter__()
if self._fd is None:
# Make certain the file exists and that we have access to it.
dirPath = os.path.dirname(self.path)
# Make certain the directory exists.
# N.B.: The names may not be sanitized for use with a shell!
if not os.access(dirPath, os.F_OK):
cmd = Command(["mkdir", "-p", dirPath])
cmd.run()
# Make certain the target exists.
if not os.access(self.path, os.F_OK):
self._createFile()
return self
######################################################################
# Protected methods
######################################################################
def _createFile(self):
"""Creates the targe file."""
# N.B.: The names may not be sanitized for use with a shell!
cmd = Command(["touch", self.path])
cmd.run()
######################################################################
# Private methods
######################################################################
########################################################################
class FileOpen(FileTouch):
"""The FileOpen object; provides basic access to a file.
Class attributes:
log (logging.Logger) - logger for this class
Attributes:
None
"""
log = logging.getLogger('utils.FileOpen')
######################################################################
# Public methods
######################################################################
@property
def file(self):
return self.__file
######################################################################
def flush(self):
self.file.flush()
######################################################################
def read(self, numberOfBytes = -1):
return self.file.read(numberOfBytes)
######################################################################
def readline(self, numberOfBytes = -1):
return self.file.readline(numberOfBytes)
######################################################################
def readlines(self, numberOfBytesHint = None):
# The documentation for readlines is not consistent with the other
# read methods as to what constitutes a valid default parameter.
# Testing shows that neither None nor -1 are acceptable so we
# use None and specifically check for it.
if numberOfBytesHint is None:
return self.file.readlines()
else:
return self.file.readlines(numberOfBytesHint)
######################################################################
def seek(self, offset, whence = os.SEEK_SET):
self.file.seek(offset, whence)
######################################################################
def truncate(self, size = None):
# The documentation for truncate indicates that without an argument
# it truncates to the current file position. Testing shows that
# neither None nor -1 are acceptable as parameters so we use None
# and specifically check for it.
if size is None:
self.file.truncate()
else:
self.file.truncate(size)
######################################################################
def write(self, string):
self.file.write(string)
######################################################################
def writelines(self, sequenceOfStrings):
self.file.writeline(sequenceOfStrings)
######################################################################
# Overridden methods
######################################################################
def next(self):
return self.file.next()
######################################################################
def __enter__(self):
"""Open the file and return ourself."""
super(FileOpen, self).__enter__()
if self._fd is None:
self._fd = os.open(self.path, self._osMode)
self.__file = os.fdopen(self._fd, self.__mode)
return self
######################################################################
def __exit__(self, exceptionType, exceptionValue, traceback):
""" Close the file."""
self.file.close()
return super(FileOpen, self).__exit__(exceptionType,
exceptionValue,
traceback)
######################################################################
def __init__(self, filePath, mode = "r", *args, **kwargs):
"""
Arguments:
None
Returns:
Nothing
"""
super(FileOpen, self).__init__(filePath, *args, **kwargs)
osMode = None
if (len(mode) > 1) and ("+" in mode[1:]):
osMode = os.O_RDWR
elif mode[0] == "r":
osMode = os.O_RDONLY
elif mode[0] == "w":
osMode = os.O_WRONLY | os.O_TRUNC
else:
osMode = os.O_RDWR
if mode[0] == "a":
osMode = osMode | os.O_APPEND
self.__file = None
self.__mode = mode
self.__osMode = osMode
######################################################################
def __iter__(self):
return self
######################################################################
# Protected methods
######################################################################
@property
def _osMode(self):
return self.__osMode
######################################################################
# Private methods
######################################################################
########################################################################
class FileLock(FileOpen):
"""The FileLock object; a context manager providing interlocked access on
a file.
The file is created, if necessary.
Class attributes:
log (logging.Logger) - logger for this class
Attributes:
_timeout - timeout in seconds (None = no timeout)
"""
log = logging.getLogger('utils.FileLock')
######################################################################
# Public methods
######################################################################
######################################################################
# Overridden methods
######################################################################
def __init__(self, filePath, mode, timeout=None, *args, **kwargs):
"""
Arguments:
filePath - (str) path to file
mode - (str) open mode
timeout - (int) timeout in seconds; may be None
Returns:
Nothing
"""
super(FileLock, self).__init__(filePath, mode)
self._timeout = timeout
######################################################################
def __enter__(self):
"""If the open mode is read-only the file is locked shared else it is
locked exclusively.
"""
super(FileLock, self).__enter__()
if self._osMode == os.O_RDONLY:
flockMode = fcntl.LOCK_SH
lockModeString = "shared"
else:
flockMode = fcntl.LOCK_EX
lockModeString = "exclusive"
if self._timeout is not None:
self.log.debug("attempting to lock {f} in {s}s mode {m}"
.format(f=self.path,
s=self._timeout,
m=lockModeString))
with Timeout(self._timeout, _(
"Could not lock {f} in {s} seconds").format(f=self.path,
s=self._timeout)):
fcntl.flock(self.file, flockMode)
else:
self.log.debug("attempting to lock {f} mode {m}"
.format(f=self.path,
m=lockModeString))
fcntl.flock(self.file, flockMode)
return self
######################################################################
def __exit__(self, exceptionType, exceptionValue, traceback):
""" Unlocks and closes the file."""
fcntl.flock(self.file, fcntl.LOCK_UN)
if exceptionType is not TimeoutError:
self.log.debug("released lock {f}".format(f=self.path))
return super(FileLock, self).__exit__(exceptionType,
exceptionValue,
traceback)
######################################################################
# Protected methods
######################################################################
######################################################################
# Private methods
######################################################################
########################################################################
class FileTemp(FileOpen):
"""The FileTemp object; a context manager providing temporary files
with specified (or default) owner and permissions.
An optional destination parameter specifies the location to which the
temp file should be moved at exit, if no exception is encountered.
The move, if specified, is performed after performing the owner
manipulations.
Class attributes:
log (logging.Logger) - logger for this class
Attributes:
None
"""
log = logging.getLogger('utils.FileTemp')
######################################################################
# Public methods
######################################################################
######################################################################
# Overridden methods
######################################################################
def __init__(self, owner = None, ownerPerm = None, destination = None,
*args, **kwargs):
"""
Arguments:
owner - (str) the owner to set for the file
ownerPerm - (str) the permissions to set for the owner
destination (str) the path to which to move the temp file on exit
Returns:
Nothing
"""
tmpFile = tempfile.mkstemp()
super(FileTemp, self).__init__(tmpFile[1], "r+", fd = tmpFile[0])
if not owner:
owner = str(os.geteuid())
if not ownerPerm:
ownerPerm = "rw"
self.__owner = owner
self.__ownerPerm = ownerPerm
self.__destination = destination
######################################################################
def __exit__(self, exceptionType, exceptionValue, traceback):
exception = exceptionType
if (exception is None) and (self.__destination is not None):
try:
cmd = Command(["chown", self.__owner, self.path])
cmd.run()
cmd = Command(["chmod", "=".join(["u", self.__ownerPerm]), self.path])
cmd.run()
self.file.close() # need to close the file to save data before move.
cmd = Command(["mv", self.path, self.__destination])
cmd.run()
except Exception as ex:
exception = ex
if (exception is not None) or (self.__destination is None):
try:
cmd = Command(["rm", self.path])
cmd.run()
except:
pass
return super(FileTemp, self).__exit__(exceptionType,
exceptionValue,
traceback)
######################################################################
# Protected methods
######################################################################
######################################################################
# Private methods
######################################################################