imip-agent

imiptools/filesys.py

715:a86a95af4a27
2015-09-10 Paul Boddie Switched back to using mkdir instead of makedirs when locking. When unlocking, move the lock directory away before removing it.
     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     old_lock_name = "__unlock__"    86     87     def __init__(self, store_dir):    88         self.store_dir = store_dir    89         if not exists(self.store_dir):    90             makedirs(self.store_dir)    91             fix_permissions(self.store_dir, True)    92         self.lock_depth = 0    93     94     def get_file_object(self, base, *parts):    95     96         """    97         Within the given 'base' location, return a path corresponding to the    98         given 'parts'.    99         """   100    101         # Handle "empty" components.   102    103         pathname = join(base, *parts)   104         return check_dir(base, pathname) and pathname or None   105    106     def get_object_in_store(self, *parts):   107    108         """   109         Return the name of any valid object stored within a hierarchy specified   110         by the given 'parts'.   111         """   112    113         parent = expected = self.store_dir   114    115         # Handle "empty" components.   116    117         parts = [p for p in parts if p]   118    119         for part in parts:   120             filename = self.get_file_object(expected, part)   121             if not filename:   122                 return False   123             parent = expected   124             expected = filename   125    126         if not exists(parent):   127             make_path(self.store_dir, parts[:-1])   128    129         return filename   130    131     def move_object(self, source, target):   132    133         "Move 'source' to 'target'."   134    135         if not self.ensure_parent(target):   136             return False   137         rename(source, target)   138    139     def ensure_parent(self, target):   140    141         "Ensure that the parent of 'target' exists."   142    143         parts = remaining_parts(self.store_dir, target)   144         if not parts or not self.get_file_object(self.store_dir, *parts[:-1]):   145             return False   146    147         make_path(self.store_dir, parts[:-1])   148         return True   149    150     # Locking methods.   151     # This uses the directory creation method exploited by MoinMoin.util.lock.   152     # However, a simple single lock type mechanism is employed here.   153    154     def make_lock_dir(self, *parts):   155    156         "Make the lock directory defined by the given 'parts'."   157    158         parts = parts and list(parts) or []   159         parts.append(self.lock_name)   160         mkdir(self.get_object_in_store(*parts))   161         parts.append(str(getpid()))   162         mkdir(self.get_object_in_store(*parts))   163    164     def remove_lock_dir(self, *parts):   165    166         "Remove the lock directory defined by the given 'parts'."   167    168         parts = parts and list(parts) or []   169         target = parts[:]   170    171         # Move the directory.   172    173         parts.append(self.lock_name)   174         target.append(self.old_lock_name)   175         rename(self.get_object_in_store(*parts), self.get_object_in_store(*target))   176    177         # Then remove the moved directory and its contents.   178    179         target.append(str(getpid()))   180         rmdir(self.get_object_in_store(*target))   181         target.pop()   182         rmdir(self.get_object_in_store(*target))   183    184     def owning_lock_dir(self, *parts):   185    186         "Return whether this process owns the lock directory."   187    188         parts = parts and list(parts) or []   189         parts.append(self.lock_name)   190         parts.append(str(getpid()))   191         return exists(self.get_object_in_store(*parts))   192    193     def acquire_lock(self, timeout=None, *parts):   194    195         """   196         Acquire an exclusive lock on the directory or a path within it described   197         by 'parts'.   198         """   199    200         start = now = time()   201    202         while not timeout or now - start < timeout:   203             try:   204                 self.make_lock_dir(*parts)   205                 break   206             except OSError, exc:   207                 if exc.errno != errno.EEXIST:   208                     raise   209                 elif self.owning_lock_dir(*parts):   210                     self.lock_depth += 1   211                     break   212             sleep(1)   213             now = time()   214    215     def release_lock(self, *parts):   216    217         """   218         Release an acquired lock on the directory or a path within it described   219         by 'parts'.   220         """   221    222         try:   223             if self.lock_depth != 0:   224                 self.lock_depth -= 1   225             else:   226                 self.remove_lock_dir(*parts)   227         except OSError, exc:   228             if exc.errno not in (errno.ENOENT, errno.ENOTEMPTY):   229                 raise   230    231 # vim: tabstop=4 expandtab shiftwidth=4