imip-agent

imiptools/filesys.py

1436:ffcb65f7b8e5
2018-01-12 Paul Boddie Added support for rule editing, also tidying up command processing and adding support for loading stored objects using identifier details. Added an option to show the store and preferences configuration. client-editing-simplification
     1 #!/usr/bin/env python     2      3 """     4 Filesystem utilities.     5      6 Copyright (C) 2014, 2015, 2017 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 settings    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 DEFAULT_PERMISSIONS = settings["DEFAULT_PERMISSIONS"]    29 DEFAULT_DIR_PERMISSIONS = settings["DEFAULT_DIR_PERMISSIONS"]    30     31 def check_dir(base, filename):    32     33     "Return whether 'base' contains 'filename'."    34     35     return commonprefix([base, abspath(filename)]) == base    36     37 def remaining_parts(base, filename):    38     39     "Return the remaining parts from 'base' provided by 'filename'."    40     41     if not check_dir(base, filename):    42         return None    43     44     filename = abspath(filename)    45     46     parts = []    47     while True:    48         filename, part = split(filename)    49         if check_dir(base, filename):    50             parts.insert(0, part)    51         else:    52             break    53     54     return parts    55     56 def fix_permissions(filename, is_dir=False):    57     58     """    59     Fix permissions for 'filename', with 'is_dir' indicating whether the object    60     should be a directory or not.    61     """    62     63     try:    64         chmod(filename, is_dir and DEFAULT_DIR_PERMISSIONS or DEFAULT_PERMISSIONS)    65     except OSError:    66         pass    67     68 def make_path(base, parts):    69     70     """    71     Make the path within 'base' having components defined by the given 'parts'.    72     Note that this function does not check the parts for suitability. To do so,    73     use the FileBase methods instead.    74     """    75     76     for part in parts:    77         pathname = join(base, part)    78         if not exists(pathname):    79             mkdir(pathname)    80             fix_permissions(pathname, True)    81         base = pathname    82     83 class FileBase:    84     85     "Basic filesystem operations."    86     87     lock_name = "__lock__"    88     89     def __init__(self, store_dir):    90         self.store_dir = abspath(store_dir)    91         if not exists(self.store_dir):    92             makedirs(self.store_dir)    93             fix_permissions(self.store_dir, True)    94         self.lock_depth = 0    95     96     def get_file_object(self, base, *parts):    97     98         """    99         Within the given 'base' location, return a path corresponding to the   100         given 'parts'.   101         """   102    103         # Handle "empty" components.   104    105         pathname = join(base, *parts)   106         return check_dir(base, pathname) and pathname or None   107    108     def get_object_in_store(self, *parts):   109    110         """   111         Return the name of any valid object stored within a hierarchy specified   112         by the given 'parts'.   113         """   114    115         parent = expected = self.store_dir   116    117         # Handle "empty" components.   118    119         parts = [p for p in parts if p]   120    121         for part in parts:   122             filename = self.get_file_object(expected, part)   123             if not filename:   124                 return None   125             parent = expected   126             expected = filename   127    128         if not exists(parent):   129             make_path(self.store_dir, parts[:-1])   130    131         return filename   132    133     def move_object(self, source, target):   134    135         "Move 'source' to 'target'."   136    137         if not self.ensure_parent(target):   138             return False   139         rename(source, target)   140    141     def ensure_parent(self, target):   142    143         "Ensure that the parent of 'target' exists."   144    145         parts = remaining_parts(self.store_dir, target)   146         if not parts or not self.get_file_object(self.store_dir, *parts[:-1]):   147             return False   148    149         make_path(self.store_dir, parts[:-1])   150         return True   151    152     # Locking methods.   153     # This uses the directory creation method exploited by MoinMoin.util.lock.   154     # However, a simple single lock type mechanism is employed here.   155    156     def make_lock_dir(self, *parts):   157    158         "Make the lock directory defined by the given 'parts'."   159    160         parts = parts and list(parts) or []   161         parts.append(self.lock_name)   162         d = self.get_object_in_store(*parts)   163         if not d: raise OSError(errno.ENOENT, "Could not get lock in store: %r in %r" % (parts, self.store_dir))   164         mkdir(d)   165         parts.append(str(getpid()))   166         d = self.get_object_in_store(*parts)   167         if not d: raise OSError(errno.ENOENT, "Could not get lock in store: %r in %r" % (parts, self.store_dir))   168         mkdir(d)   169    170     def remove_lock_dir(self, *parts):   171    172         "Remove the lock directory defined by the given 'parts'."   173    174         parts = parts and list(parts) or []   175    176         parts.append(self.lock_name)   177         parts.append(str(getpid()))   178         rmdir(self.get_object_in_store(*parts))   179         parts.pop()   180         rmdir(self.get_object_in_store(*parts))   181    182     def owning_lock_dir(self, *parts):   183    184         "Return whether this process owns the lock directory."   185    186         parts = parts and list(parts) or []   187         parts.append(self.lock_name)   188         parts.append(str(getpid()))   189         return exists(self.get_object_in_store(*parts))   190    191     def acquire_lock(self, timeout=None, *parts):   192    193         """   194         Acquire an exclusive lock on the directory or a path within it described   195         by 'parts'.   196         """   197    198         start = now = time()   199    200         while not timeout or now - start < timeout:   201             try:   202                 self.make_lock_dir(*parts)   203                 break   204             except OSError, exc:   205                 if exc.errno != errno.EEXIST:   206                     raise   207                 elif self.owning_lock_dir(*parts):   208                     self.lock_depth += 1   209                     break   210             sleep(1)   211             now = time()   212    213     def release_lock(self, *parts):   214    215         """   216         Release an acquired lock on the directory or a path within it described   217         by 'parts'.   218         """   219    220         try:   221             if self.lock_depth != 0:   222                 self.lock_depth -= 1   223             else:   224                 self.remove_lock_dir(*parts)   225         except OSError, exc:   226             if exc.errno not in (errno.ENOENT, errno.ENOTEMPTY):   227                 raise   228    229 # vim: tabstop=4 expandtab shiftwidth=4