a3470f
From a9db68fc1f05639cb79defef6ed7da58572113ea Mon Sep 17 00:00:00 2001
a3470f
From: Kotresh HR <khiremat@redhat.com>
a3470f
Date: Thu, 5 Jul 2018 07:07:38 -0400
a3470f
Subject: [PATCH 328/333] geo-rep: Fix issues with gfid conflict handling
a3470f
a3470f
1. MKDIR/RMDIR is recorded on all bricks. So if
a3470f
   one brick succeeds creating it, other bricks
a3470f
   should ignore it. But this was not happening.
a3470f
   The fix rename of directories in hybrid crawl,
a3470f
   was trying to rename the directory to itself
a3470f
   and in the process crashing with ENOENT if the
a3470f
   directory is removed.
a3470f
a3470f
2. If file is created, deleted and a directory is
a3470f
   created with same name, it was failing to sync.
a3470f
   Again the issue is around the fix for rename
a3470f
   of directories in hybrid crawl. Fixed the same.
a3470f
a3470f
   If the same case was done with hardlink present
a3470f
   for the file, it was failing. This patch fixes
a3470f
   that too.
a3470f
a3470f
Backport of
a3470f
  > Patch: https://review.gluster.org/#/c/20473/
a3470f
  > fixes: bz#1598884
a3470f
  > Change-Id: I6f3bca44e194e415a3d4de3b9d03cc8976439284
a3470f
  > Signed-off-by: Kotresh HR <khiremat@redhat.com>
a3470f
a3470f
BUG: 1598384
a3470f
Change-Id: I6f3bca44e194e415a3d4de3b9d03cc8976439284
a3470f
Signed-off-by: Kotresh HR <khiremat@redhat.com>
a3470f
Reviewed-on: https://code.engineering.redhat.com/gerrit/143400
a3470f
Tested-by: RHGS Build Bot <nigelb@redhat.com>
a3470f
Reviewed-by: Aravinda Vishwanathapura Krishna Murthy <avishwan@redhat.com>
a3470f
Reviewed-by: Sunil Kumar Heggodu Gopala Acharya <sheggodu@redhat.com>
a3470f
---
a3470f
 geo-replication/syncdaemon/master.py     | 157 ++++++++++++++++++++++---------
a3470f
 geo-replication/syncdaemon/resource.py   |  57 ++++++-----
a3470f
 geo-replication/syncdaemon/syncdutils.py |  35 +++++++
a3470f
 3 files changed, 180 insertions(+), 69 deletions(-)
a3470f
a3470f
diff --git a/geo-replication/syncdaemon/master.py b/geo-replication/syncdaemon/master.py
a3470f
index 64e9836..1399378 100644
a3470f
--- a/geo-replication/syncdaemon/master.py
a3470f
+++ b/geo-replication/syncdaemon/master.py
a3470f
@@ -692,7 +692,8 @@ class GMasterChangelogMixin(GMasterCommon):
a3470f
     TYPE_GFID = "D "
a3470f
     TYPE_ENTRY = "E "
a3470f
 
a3470f
-    MAX_EF_RETRIES = 15
a3470f
+    MAX_EF_RETRIES = 10
a3470f
+    MAX_OE_RETRIES = 5
a3470f
 
a3470f
     # flat directory hierarchy for gfid based access
a3470f
     FLAT_DIR_HIERARCHY = '.'
a3470f
@@ -788,38 +789,53 @@ class GMasterChangelogMixin(GMasterCommon):
a3470f
 
a3470f
         self.status.inc_value("failures", num_failures)
a3470f
 
a3470f
-    def fix_possible_entry_failures(self, failures, retry_count):
a3470f
+    def fix_possible_entry_failures(self, failures, retry_count, entries):
a3470f
         pfx = gauxpfx()
a3470f
         fix_entry_ops = []
a3470f
         failures1 = []
a3470f
         for failure in failures:
a3470f
-            if failure[2]['dst']:
a3470f
+            if failure[2]['name_mismatch']:
a3470f
+                pbname = failure[2]['slave_entry']
a3470f
+            elif failure[2]['dst']:
a3470f
                 pbname = failure[0]['entry1']
a3470f
             else:
a3470f
                 pbname = failure[0]['entry']
a3470f
-            if failure[2]['gfid_mismatch']:
a3470f
+
a3470f
+            op = failure[0]['op']
a3470f
+            # name exists but gfid is different
a3470f
+            if failure[2]['gfid_mismatch'] or failure[2]['name_mismatch']:
a3470f
                 slave_gfid = failure[2]['slave_gfid']
a3470f
                 st = lstat(os.path.join(pfx, slave_gfid))
a3470f
+                # Takes care of scenarios with no hardlinks
a3470f
                 if isinstance(st, int) and st == ENOENT:
a3470f
-                    logging.info(lf('Fixing gfid mismatch in slave. Deleting'
a3470f
-                                    ' the entry', retry_count=retry_count,
a3470f
+                    logging.info(lf('Entry not present on master. Fixing gfid '
a3470f
+                                    'mismatch in slave. Deleting the entry',
a3470f
+                                    retry_count=retry_count,
a3470f
                                     entry=repr(failure)))
a3470f
-                    #Add deletion to fix_entry_ops list
a3470f
+                    # Add deletion to fix_entry_ops list
a3470f
                     if failure[2]['slave_isdir']:
a3470f
-                        fix_entry_ops.append(edct('RMDIR',
a3470f
-                                                  gfid=failure[2]['slave_gfid'],
a3470f
-                                                  entry=pbname))
a3470f
+                        fix_entry_ops.append(
a3470f
+                            edct('RMDIR',
a3470f
+                                 gfid=failure[2]['slave_gfid'],
a3470f
+                                 entry=pbname))
a3470f
                     else:
a3470f
-                        fix_entry_ops.append(edct('UNLINK',
a3470f
-                                                  gfid=failure[2]['slave_gfid'],
a3470f
-                                                  entry=pbname))
a3470f
+                        fix_entry_ops.append(
a3470f
+                            edct('UNLINK',
a3470f
+                                 gfid=failure[2]['slave_gfid'],
a3470f
+                                 entry=pbname))
a3470f
+                # Takes care of scenarios of hardlinks/renames on master
a3470f
                 elif not isinstance(st, int):
a3470f
-                    #The file exists on master but with different name.
a3470f
-                    #Probabaly renamed and got missed during xsync crawl.
a3470f
-                    if failure[2]['slave_isdir']:
a3470f
-                        logging.info(lf('Fixing gfid mismatch in slave',
a3470f
+                    if matching_disk_gfid(slave_gfid, pbname):
a3470f
+                        # Safe to ignore the failure as master contains same
a3470f
+                        # file with same gfid. Remove entry from entries list
a3470f
+                        logging.info(lf('Fixing gfid mismatch in slave. '
a3470f
+                                        ' Safe to ignore, take out entry',
a3470f
                                         retry_count=retry_count,
a3470f
                                         entry=repr(failure)))
a3470f
+                        entries.remove(failure[0])
a3470f
+                    # The file exists on master but with different name.
a3470f
+                    # Probably renamed and got missed during xsync crawl.
a3470f
+                    elif failure[2]['slave_isdir']:
a3470f
                         realpath = os.readlink(os.path.join(gconf.local_path,
a3470f
                                                             ".glusterfs",
a3470f
                                                             slave_gfid[0:2],
a3470f
@@ -827,64 +843,99 @@ class GMasterChangelogMixin(GMasterCommon):
a3470f
                                                             slave_gfid))
a3470f
                         dst_entry = os.path.join(pfx, realpath.split('/')[-2],
a3470f
                                                  realpath.split('/')[-1])
a3470f
-                        rename_dict = edct('RENAME', gfid=slave_gfid,
a3470f
-                                           entry=failure[0]['entry'],
a3470f
-                                           entry1=dst_entry, stat=st,
a3470f
-                                           link=None)
a3470f
-                        logging.info(lf('Fixing gfid mismatch in slave. '
a3470f
-                                        'Renaming', retry_count=retry_count,
a3470f
-                                        entry=repr(rename_dict)))
a3470f
-                        fix_entry_ops.append(rename_dict)
a3470f
+                        src_entry = pbname
a3470f
+                        logging.info(lf('Fixing dir name/gfid mismatch in '
a3470f
+                                        'slave', retry_count=retry_count,
a3470f
+                                        entry=repr(failure)))
a3470f
+                        if src_entry == dst_entry:
a3470f
+                            # Safe to ignore the failure as master contains
a3470f
+                            # same directory as in slave with same gfid.
a3470f
+                            # Remove the failure entry from entries list
a3470f
+                            logging.info(lf('Fixing dir name/gfid mismatch'
a3470f
+                                            ' in slave. Safe to ignore, '
a3470f
+                                            'take out entry',
a3470f
+                                            retry_count=retry_count,
a3470f
+                                            entry=repr(failure)))
a3470f
+                            entries.remove(failure[0])
a3470f
+                        else:
a3470f
+                            rename_dict = edct('RENAME', gfid=slave_gfid,
a3470f
+                                               entry=src_entry,
a3470f
+                                               entry1=dst_entry, stat=st,
a3470f
+                                               link=None)
a3470f
+                            logging.info(lf('Fixing dir name/gfid mismatch'
a3470f
+                                            ' in slave. Renaming',
a3470f
+                                            retry_count=retry_count,
a3470f
+                                            entry=repr(rename_dict)))
a3470f
+                            fix_entry_ops.append(rename_dict)
a3470f
                     else:
a3470f
-                        logging.info(lf('Fixing gfid mismatch in slave. '
a3470f
-                                        ' Deleting the entry',
a3470f
+                        # A hardlink file exists with different name or
a3470f
+                        # renamed file exists and we are sure from
a3470f
+                        # matching_disk_gfid check that the entry doesn't
a3470f
+                        # exist with same gfid so we can safely delete on slave
a3470f
+                        logging.info(lf('Fixing file gfid mismatch in slave. '
a3470f
+                                        'Hardlink/Rename Case. Deleting entry',
a3470f
+                                        retry_count=retry_count,
a3470f
+                                        entry=repr(failure)))
a3470f
+                        fix_entry_ops.append(
a3470f
+                            edct('UNLINK',
a3470f
+                                 gfid=failure[2]['slave_gfid'],
a3470f
+                                 entry=pbname))
a3470f
+            elif failure[1] == ENOENT:
a3470f
+                # Ignore ENOENT error for fix_entry_ops aka retry_count > 1
a3470f
+                if retry_count > 1:
a3470f
+                    logging.info(lf('ENOENT error while fixing entry ops. '
a3470f
+                                    'Safe to ignore, take out entry',
a3470f
+                                    retry_count=retry_count,
a3470f
+                                    entry=repr(failure)))
a3470f
+                    entries.remove(failure[0])
a3470f
+                elif op in ('MKNOD', 'CREATE', 'MKDIR'):
a3470f
+                    pargfid = pbname.split('/')[1]
a3470f
+                    st = lstat(os.path.join(pfx, pargfid))
a3470f
+                    # Safe to ignore the failure as master doesn't contain
a3470f
+                    # parent directory.
a3470f
+                    if isinstance(st, int):
a3470f
+                        logging.info(lf('Fixing ENOENT error in slave. Parent '
a3470f
+                                        'does not exist on master. Safe to '
a3470f
+                                        'ignore, take out entry',
a3470f
                                         retry_count=retry_count,
a3470f
                                         entry=repr(failure)))
a3470f
-                        fix_entry_ops.append(edct('UNLINK',
a3470f
-                                                  gfid=failure[2]['slave_gfid'],
a3470f
-                                                  entry=pbname))
a3470f
-                        logging.error(lf('Entry cannot be fixed in slave due '
a3470f
-                                         'to GFID mismatch, find respective '
a3470f
-                                         'path for the GFID and trigger sync',
a3470f
-                                         gfid=slave_gfid))
a3470f
+                        entries.remove(failure[0])
a3470f
 
a3470f
         if fix_entry_ops:
a3470f
-            #Process deletions of entries whose gfids are mismatched
a3470f
+            # Process deletions of entries whose gfids are mismatched
a3470f
             failures1 = self.slave.server.entry_ops(fix_entry_ops)
a3470f
-            if not failures1:
a3470f
-                logging.info ("Sucessfully fixed entry ops with gfid mismatch")
a3470f
 
a3470f
-        return failures1
a3470f
+        return (failures1, fix_entry_ops)
a3470f
 
a3470f
     def handle_entry_failures(self, failures, entries):
a3470f
         retries = 0
a3470f
         pending_failures = False
a3470f
         failures1 = []
a3470f
         failures2 = []
a3470f
+        entry_ops1 = []
a3470f
+        entry_ops2 = []
a3470f
 
a3470f
         if failures:
a3470f
             pending_failures = True
a3470f
             failures1 = failures
a3470f
+            entry_ops1 = entries
a3470f
 
a3470f
             while pending_failures and retries < self.MAX_EF_RETRIES:
a3470f
                 retries += 1
a3470f
-                failures2 = self.fix_possible_entry_failures(failures1,
a3470f
-                                                             retries)
a3470f
+                (failures2, entry_ops2) = self.fix_possible_entry_failures(
a3470f
+                    failures1, retries, entry_ops1)
a3470f
                 if not failures2:
a3470f
                     pending_failures = False
a3470f
+                    logging.info(lf('Sucessfully fixed entry ops with gfid '
a3470f
+                                 'mismatch', retry_count=retries))
a3470f
                 else:
a3470f
                     pending_failures = True
a3470f
                     failures1 = failures2
a3470f
+                    entry_ops1 = entry_ops2
a3470f
 
a3470f
             if pending_failures:
a3470f
                 for failure in failures1:
a3470f
                     logging.error("Failed to fix entry ops %s", repr(failure))
a3470f
-            else:
a3470f
-                #Retry original entry list 5 times
a3470f
-                failures = self.slave.server.entry_ops(entries)
a3470f
-
a3470f
-            self.log_failures(failures, 'gfid', gauxpfx(), 'ENTRY')
a3470f
-
a3470f
 
a3470f
     def process_change(self, change, done, retry):
a3470f
         pfx = gauxpfx()
a3470f
@@ -1112,7 +1163,19 @@ class GMasterChangelogMixin(GMasterCommon):
a3470f
             self.status.inc_value("entry", len(entries))
a3470f
 
a3470f
             failures = self.slave.server.entry_ops(entries)
a3470f
-            self.handle_entry_failures(failures, entries)
a3470f
+            count = 0
a3470f
+            while failures and count < self.MAX_OE_RETRIES:
a3470f
+                count += 1
a3470f
+                self.handle_entry_failures(failures, entries)
a3470f
+                logging.info("Retry original entries. count = %s" % count)
a3470f
+                failures = self.slave.server.entry_ops(entries)
a3470f
+                if not failures:
a3470f
+                    logging.info("Sucessfully fixed all entry ops with gfid "
a3470f
+                                 "mismatch")
a3470f
+                    break
a3470f
+
a3470f
+            self.log_failures(failures, 'gfid', gauxpfx(), 'ENTRY')
a3470f
+
a3470f
             self.status.dec_value("entry", len(entries))
a3470f
 
a3470f
             # Update Entry stime in Brick Root only in case of Changelog mode
a3470f
diff --git a/geo-replication/syncdaemon/resource.py b/geo-replication/syncdaemon/resource.py
a3470f
index 0d5462a..eb696f3 100644
a3470f
--- a/geo-replication/syncdaemon/resource.py
a3470f
+++ b/geo-replication/syncdaemon/resource.py
a3470f
@@ -38,9 +38,9 @@ from syncdutils import CHANGELOG_AGENT_CLIENT_VERSION
a3470f
 from syncdutils import GX_GFID_CANONICAL_LEN
a3470f
 from gsyncdstatus import GeorepStatus
a3470f
 from syncdutils import get_master_and_slave_data_from_args
a3470f
-from syncdutils import lf, Popen, sup, Volinfo
a3470f
+from syncdutils import lf, Popen, sup
a3470f
 from syncdutils import Xattr, matching_disk_gfid, get_gfid_from_mnt
a3470f
-from syncdutils import unshare_propagation_supported
a3470f
+from syncdutils import unshare_propagation_supported, get_slv_dir_path
a3470f
 
a3470f
 UrlRX = re.compile('\A(\w+)://([^ *?[]*)\Z')
a3470f
 HostRX = re.compile('[a-zA-Z\d](?:[a-zA-Z\d.-]*[a-zA-Z\d])?', re.I)
a3470f
@@ -50,7 +50,6 @@ ENOTSUP = getattr(errno, 'ENOTSUP', 'EOPNOTSUPP')
a3470f
 
a3470f
 slv_volume = None
a3470f
 slv_host = None
a3470f
-slv_bricks = None
a3470f
 
a3470f
 def desugar(ustr):
a3470f
     """transform sugared url strings to standard <scheme>://<urlbody> form
a3470f
@@ -463,13 +462,23 @@ class Server(object):
a3470f
             # to be purged is the GFID gotten from the changelog.
a3470f
             # (a stat(changelog_gfid) would also be valid here)
a3470f
             # The race here is between the GFID check and the purge.
a3470f
+
a3470f
+            # If the entry or the gfid of the file to be deleted is not present
a3470f
+            # on slave, we can ignore the unlink/rmdir
a3470f
+            if isinstance(lstat(entry), int) or \
a3470f
+               isinstance(lstat(os.path.join(pfx, gfid)), int):
a3470f
+                return
a3470f
+
a3470f
             if not matching_disk_gfid(gfid, entry):
a3470f
                 collect_failure(e, EEXIST)
a3470f
                 return
a3470f
 
a3470f
             if op == 'UNLINK':
a3470f
                 er = errno_wrap(os.unlink, [entry], [ENOENT, ESTALE], [EBUSY])
a3470f
-                return er
a3470f
+                # EISDIR is safe error, ignore. This can only happen when
a3470f
+                # unlink is sent from master while fixing gfid conflicts.
a3470f
+                if er != EISDIR:
a3470f
+                    return er
a3470f
 
a3470f
             elif op == 'RMDIR':
a3470f
                 er = errno_wrap(os.rmdir, [entry], [ENOENT, ESTALE,
a3470f
@@ -480,7 +489,11 @@ class Server(object):
a3470f
         def collect_failure(e, cmd_ret, dst=False):
a3470f
             slv_entry_info = {}
a3470f
             slv_entry_info['gfid_mismatch'] = False
a3470f
+            slv_entry_info['name_mismatch'] = False
a3470f
             slv_entry_info['dst'] = dst
a3470f
+            slv_entry_info['slave_isdir'] = False
a3470f
+            slv_entry_info['slave_name'] = None
a3470f
+            slv_entry_info['slave_gfid'] = None
a3470f
             # We do this for failing fops on Slave
a3470f
             # Master should be logging this
a3470f
             if cmd_ret is None:
a3470f
@@ -498,6 +511,9 @@ class Server(object):
a3470f
                     if not isinstance(st, int):
a3470f
                         if st and stat.S_ISDIR(st.st_mode):
a3470f
                             slv_entry_info['slave_isdir'] = True
a3470f
+                            dir_name = get_slv_dir_path(slv_host, slv_volume,
a3470f
+                                                        disk_gfid)
a3470f
+                            slv_entry_info['slave_name'] = dir_name
a3470f
                         else:
a3470f
                             slv_entry_info['slave_isdir'] = False
a3470f
                     slv_entry_info['slave_gfid'] = disk_gfid
a3470f
@@ -618,37 +634,34 @@ class Server(object):
a3470f
                                          [ENOENT, EEXIST], [ESTALE])
a3470f
                     collect_failure(e, cmd_ret)
a3470f
             elif op == 'MKDIR':
a3470f
+                en = e['entry']
a3470f
                 slink = os.path.join(pfx, gfid)
a3470f
                 st = lstat(slink)
a3470f
                 # don't create multiple entries with same gfid
a3470f
                 if isinstance(st, int):
a3470f
                     blob = entry_pack_mkdir(
a3470f
                         gfid, bname, e['mode'], e['uid'], e['gid'])
a3470f
-                else:
a3470f
+                elif (isinstance(lstat(en), int) or
a3470f
+                      not matching_disk_gfid(gfid, en)):
a3470f
                     # If gfid of a directory exists on slave but path based
a3470f
                     # create is getting EEXIST. This means the directory is
a3470f
                     # renamed in master but recorded as MKDIR during hybrid
a3470f
                     # crawl. Get the directory path by reading the backend
a3470f
                     # symlink and trying to rename to new name as said by
a3470f
                     # master.
a3470f
-                    global slv_bricks
a3470f
-                    global slv_volume
a3470f
-                    global slv_host
a3470f
-                    if not slv_bricks:
a3470f
-                        slv_info = Volinfo (slv_volume, slv_host)
a3470f
-                        slv_bricks = slv_info.bricks
a3470f
-                    # Result of readlink would be of format as below.
a3470f
-                    # readlink = "../../pgfid[0:2]/pgfid[2:4]/pgfid/basename"
a3470f
-                    realpath = os.readlink(os.path.join(slv_bricks[0]['dir'],
a3470f
-                                                        ".glusterfs", gfid[0:2],
a3470f
-                                                        gfid[2:4], gfid))
a3470f
-                    realpath_parts = realpath.split('/')
a3470f
-                    src_pargfid = realpath_parts[-2]
a3470f
-                    src_basename = realpath_parts[-1]
a3470f
-                    src_entry = os.path.join(pfx, src_pargfid, src_basename)
a3470f
                     logging.info(lf("Special case: rename on mkdir",
a3470f
-                                   gfid=gfid, entry=repr(entry)))
a3470f
-                    rename_with_disk_gfid_confirmation(gfid, src_entry, entry)
a3470f
+                                    gfid=gfid, entry=repr(entry)))
a3470f
+                    src_entry = get_slv_dir_path(slv_host, slv_volume, gfid)
a3470f
+                    if src_entry is not None and src_entry != entry:
a3470f
+                        slv_entry_info = {}
a3470f
+                        slv_entry_info['gfid_mismatch'] = False
a3470f
+                        slv_entry_info['name_mismatch'] = True
a3470f
+                        slv_entry_info['dst'] = False
a3470f
+                        slv_entry_info['slave_isdir'] = True
a3470f
+                        slv_entry_info['slave_gfid'] = gfid
a3470f
+                        slv_entry_info['slave_entry'] = src_entry
a3470f
+
a3470f
+                        failures.append((e, EEXIST, slv_entry_info))
a3470f
             elif op == 'LINK':
a3470f
                 slink = os.path.join(pfx, gfid)
a3470f
                 st = lstat(slink)
a3470f
diff --git a/geo-replication/syncdaemon/syncdutils.py b/geo-replication/syncdaemon/syncdutils.py
a3470f
index 6dafb0a..d798356 100644
a3470f
--- a/geo-replication/syncdaemon/syncdutils.py
a3470f
+++ b/geo-replication/syncdaemon/syncdutils.py
a3470f
@@ -77,6 +77,7 @@ CHANGELOG_AGENT_CLIENT_VERSION = 1.0
a3470f
 NodeID = None
a3470f
 rsync_version = None
a3470f
 unshare_mnt_propagation = None
a3470f
+slv_bricks = None
a3470f
 SPACE_ESCAPE_CHAR = "%20"
a3470f
 NEWLINE_ESCAPE_CHAR = "%0A"
a3470f
 PERCENTAGE_ESCAPE_CHAR = "%25"
a3470f
@@ -671,6 +672,40 @@ def get_rsync_version(rsync_cmd):
a3470f
     return rsync_version
a3470f
 
a3470f
 
a3470f
+def get_slv_dir_path(slv_host, slv_volume, gfid):
a3470f
+    global slv_bricks
a3470f
+
a3470f
+    dir_path = ENOENT
a3470f
+
a3470f
+    if not slv_bricks:
a3470f
+        slv_info = Volinfo(slv_volume, slv_host)
a3470f
+        slv_bricks = slv_info.bricks
a3470f
+    # Result of readlink would be of format as below.
a3470f
+    # readlink = "../../pgfid[0:2]/pgfid[2:4]/pgfid/basename"
a3470f
+    for brick in slv_bricks:
a3470f
+        dir_path = errno_wrap(os.path.join,
a3470f
+                              [brick['dir'],
a3470f
+                               ".glusterfs", gfid[0:2],
a3470f
+                               gfid[2:4],
a3470f
+                               gfid], [ENOENT], [ESTALE])
a3470f
+        if dir_path != ENOENT:
a3470f
+            break
a3470f
+
a3470f
+    if not isinstance(dir_path, int):
a3470f
+        realpath = errno_wrap(os.readlink, [dir_path],
a3470f
+                              [ENOENT], [ESTALE])
a3470f
+
a3470f
+        if not isinstance(realpath, int):
a3470f
+            realpath_parts = realpath.split('/')
a3470f
+            pargfid = realpath_parts[-2]
a3470f
+            basename = realpath_parts[-1]
a3470f
+            pfx = gauxpfx()
a3470f
+            dir_entry = os.path.join(pfx, pargfid, basename)
a3470f
+            return dir_entry
a3470f
+
a3470f
+    return None
a3470f
+
a3470f
+
a3470f
 def lf(event, **kwargs):
a3470f
     """
a3470f
     Log Format helper function, log messages can be
a3470f
-- 
a3470f
1.8.3.1
a3470f