imip-agent

imiptools/filesys.py

1026:4a0226da2137
2016-01-29 Paul Boddie Make a scheduling package to potentially support multiple scheduling modules.
     1 #!/usr/bin/env python     2      3 """     4 Filesystem utilities.     5      6 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 import errno    23 from imiptools.config import DEFAULT_PERMISSIONS, DEFAULT_DIR_PERMISSIONS    24 from os.path import abspath, commonprefix, exists, join, split    25 from os import chmod, getpid, makedirs, mkdir, rename, rmdir    26 from time import sleep, time    27     28 def check_dir(base, filename):    29     30     "Return whether 'base' contains 'filename'."    31     32     return commonprefix([base, abspath(filename)]) == base    33     34 def remaining_parts(base, filename):    35     36     "Return the remaining parts from 'base' provided by 'filename'."    37     38     if not check_dir(base, filename):    39         return None    40     41     filename = abspath(filename)    42     43     parts = []    44     while True:    45         filename, part = split(filename)    46         if check_dir(base, filename):    47             parts.insert(0, part)    48         else:    49             break    50     51     return parts    52     53 def fix_permissions(filename, is_dir=False):    54     55     """    56     Fix permissions for 'filename', with 'is_dir' indicating whether the object    57     should be a directory or not.    58     """    59     60     try:    61         chmod(filename, is_dir and DEFAULT_DIR_PERMISSIONS or DEFAULT_PERMISSIONS)    62     except OSError:    63         pass    64     65 def make_path(base, parts):    66     67     """    68     Make the path within 'base' having components defined by the given 'parts'.    69     Note that this function does not check the parts for suitability. To do so,    70     use the FileBase methods instead.    71     """    72     73     for part in parts:    74         pathname = join(base, part)    75         if not exists(pathname):    76             mkdir(pathname)    77             fix_permissions(pathname, True)    78         base = pathname    79     80 class FileBase:    81     82     "Basic filesystem operations."    83     84     lock_name = "__lock__"    85     86     def __init__(self, store_dir):    87         self.store_dir = abspath(store_dir)    88         if not exists(self.store_dir):    89             makedirs(self.store_dir)    90             fix_permissions(self.store_dir, True)    91         self.lock_depth = 0    92     93     def get_file_object(self, base, *parts):    94     95         """    96         Within the given 'base' location, return a path corresponding to the    97         given 'parts'.    98         """    99    100         # Handle "empty" components.   101    102         pathname = join(base, *parts)   103         return check_dir(base, pathname) and pathname or None   104    105     def get_object_in_store(self, *parts):   106    107         """   108         Return the name of any valid object stored within a hierarchy specified   109         by the given 'parts'.   110         """   111    112         parent = expected = self.store_dir   113    114         # Handle "empty" components.   115    116         parts = [p for p in parts if p]   117    118         for part in parts:   119             filename = self.get_file_object(expected, part)   120             if not filename:   121                 return None   122             parent = expected   123             expected = filename   124    125         if not exists(parent):   126             make_path(self.store_dir, parts[:-1])   127    128         return filename   129    130     def move_object(self, source, target):   131    132         "Move 'source' to 'target'."   133    134         if not self.ensure_parent(target):   135             return False   136         rename(source, target)   137    138     def ensure_parent(self, target):   139    140         "Ensure that the parent of 'target' exists."   141    142         parts = remaining_parts(self.store_dir, target)   143         if not parts or not self.get_file_object(self.store_dir, *parts[:-1]):   144             return False   145    146         make_path(self.store_dir, parts[:-1])   147         return True   148    149     # Locking methods.   150     # This uses the directory creation method exploited by MoinMoin.util.lock.   151     # However, a simple single lock type mechanism is employed here.   152    153     def make_lock_dir(self, *parts):   154    155         "Make the lock directory defined by the given 'parts'."   156    157         parts = parts and list(parts) or []   158         parts.append(self.lock_name)   159         d = self.get_object_in_store(*parts)   160         if not d: raise OSError(errno.ENOENT, "Could not get lock in store: %r in %r" % (parts, self.store_dir))   161         mkdir(d)   162         parts.append(str(getpid()))   163         d = self.get_object_in_store(*parts)   164         if not d: raise OSError(errno.ENOENT, "Could not get lock in store: %r in %r" % (parts, self.store_dir))   165         mkdir(d)   166    167     def remove_lock_dir(self, *parts):   168    169         "Remove the lock directory defined by the given 'parts'."   170    171         parts = parts and list(parts) or []   172    173         parts.append(self.lock_name)   174         parts.append(str(getpid()))   175         rmdir(self.get_object_in_store(*parts))   176         parts.pop()   177         rmdir(self.get_object_in_store(*parts))   178    179     def owning_lock_dir(self, *parts):   180    181         "Return whether this process owns the lock directory."   182    183         parts = parts and list(parts) or []   184         parts.append(self.lock_name)   185         parts.append(str(getpid()))   186         return exists(self.get_object_in_store(*parts))   187    188     def acquire_lock(self, timeout=None, *parts):   189    190         """   191         Acquire an exclusive lock on the directory or a path within it described   192         by 'parts'.   193         """   194    195         start = now = time()   196    197         while not timeout or now - start < timeout:   198             try:   199                 self.make_lock_dir(*parts)   200                 break   201             except OSError, exc:   202                 if exc.errno != errno.EEXIST:   203                     raise   204                 elif self.owning_lock_dir(*parts):   205                     self.lock_depth += 1   206                     break   207             sleep(1)   208             now = time()   209    210     def release_lock(self, *parts):   211    212         """   213         Release an acquired lock on the directory or a path within it described   214         by 'parts'.   215         """   216    217         try:   218             if self.lock_depth != 0:   219                 self.lock_depth -= 1   220             else:   221                 self.remove_lock_dir(*parts)   222         except OSError, exc:   223             if exc.errno not in (errno.ENOENT, errno.ENOTEMPTY):   224                 raise   225    226 # vim: tabstop=4 expandtab shiftwidth=4