// update_dir_from_tar [python]
def update_dir_from_tar(tar, root_dir):
    directories = []
    valid_paths = BloomSet()
    for entry in tar:
        # Convert entry type to stat constant
        if entry.type == tarfile.DIRTYPE:
            entry_stat_type = stat.S_IFDIR
        elif entry.type in (tarfile.REGTYPE, tarfile.LNKTYPE):
            # Coda apparently doesn't allow hard links to symlinks
            entry_stat_type = stat.S_IFREG
        elif entry.type == tarfile.SYMTYPE:
            entry_stat_type = stat.S_IFLNK
        else:
            raise ValueError(f"Unexpected file type {entry.type}")

        # Check for existing file
        path = build_path(root_dir, entry.name)
        try:
            st: Optional[os.stat_result] = os.lstat(path)
        except OSError:
            st = None

        # If entry has changed types, remove the old object
        if st is not None and stat.S_IFMT(st.st_mode) != entry_stat_type:
            if stat.S_ISDIR(st.st_mode):
                shutil.rmtree(path)
            else:
                os.unlink(path)
            st = None

        # Create parent directory if not present.  Parents are not
        # necessarily dumped before children.
        path.parent.mkdir(parents=True, exist_ok=True)

        # Create new object
        if entry.isdir():
            if not os.path.exists(path):
                print("d", path)
                os.mkdir(path)
            # Go back and set mtime after directory has been populated
            directories.append(entry)
        elif entry.isfile():
            # update_file() will break hard links if it modifies the file.
            # This is what we want because links may have also been broken
            # at the source.  codadump2tar always dumps hard links, so we
            # will rebuild any links that should still exist.
            if update_file(path, TarMemberFile(tar, entry)):
                print("f", path)
        elif entry.issym():
            if st is None or (entry.linkname and os.readlink(path) != entry.linkname):
                print("s", path)
                if st is not None:
                    os.unlink(path)
                os.symlink(entry.linkname, path)
        elif entry.islnk():
            target_path = build_path(root_dir, entry.linkname)
            target_st = os.lstat(target_path)
            if (
                st is None
                or st.st_dev != target_st.st_dev
                or st.st_ino != target_st.st_ino
            ):
                print("l", path)
                if st is not None:
                    os.unlink(path)
                os.link(target_path, path)

        # Update metadata
        attrs = XAttrs(path)
        # owner and mode.  Hardlinks were updated with the primary, and we
        # can't set xattrs on symlinks.
        if entry.isfile() or entry.isdir():
            # rsync --fake-super compatible:
            # octal_mode_with_type major,minor uid:gid
            mode = entry_stat_type | entry.mode
            attrs.update(
                ATTR_STAT,
                f"{mode:o} 0,0 {entry.uid}:{entry.gid}",
            )
        # mtime.  Directories will be updated later, and hardlinks were
        # updated with the primary.
        if (entry.isfile() or entry.issym()) and os.lstat(path).st_mtime != entry.mtime:
            lutime(path, entry.mtime)

        # Protect from garbage collection
        valid_paths.add(str(path))

    # Deferred update of directory mtimes
    for entry in directories:
        path = build_path(root_dir, entry.name)
        if os.stat(path).st_mtime != entry.mtime:
            os.utime(path, (entry.mtime, entry.mtime))

    return valid_paths