Example #1
0
    def rename(self, old, new):
        logger.debug("rename(%s, %s)", old, new)
        pathinfo_old = PathInfo(old)
        pathinfo_new = PathInfo(new)

        # Fusepy should block these possibilities:
        assert pathinfo_old != pathinfo_new
        assert not self._is_reserved_name(os.path.basename(old))

        if self._is_reserved_name(os.path.basename(new)):
            raise FuseOSError(errno.EINVAL)

        # if self._exists(new):
        #    raise FuseOSError(errno.EEXIST)

        # +---------------+----------+-------------+-------------+-----------------+
        # | Source \ Dest |  Normal  | Entry Point |     Tag     | Tagged File/Dir |
        # |               | File/Dir |             |             |                 |
        # +---------------+----------+-------------+-------------+-----------------+
        # | Normal file   | STD      | fail        | fail        | OK              |
        # | Normal dir    | STD      | OK          | OK if empty | OK              |
        # | Entry point   | OK?      | STD         | OK?         | OK?             |
        # | Tag           | OK       | OK?         | OK          | OK              |
        # | Tagged file   | OK       | fail        | fail        | OK              |
        # | Tagged folder | OK       | OK          | OK if empty | OK              |
        # +---------------+----------+-------------+-------------+-----------------+

        if pathinfo_old.is_entrypoint:
            self._move_entry_point(pathinfo_old, pathinfo_new)
        elif pathinfo_old.is_tag:
            self._move_tag(pathinfo_old, pathinfo_new)
        elif pathinfo_old.is_tagged_object:
            self._move_tagged_obj(pathinfo_old, pathinfo_new)
        else:
            self._move_standard_obj(pathinfo_old, pathinfo_new)
Example #2
0
 def test_path_type_recognition(self):
     for k in self._testpaths.keys():
         with self.subTest(k=k):
             self.assertEqual(
                 PathInfo(k).is_standard_object, self._testpaths[k][0], k)
             self.assertEqual(
                 PathInfo(k).is_tagged_object, self._testpaths[k][1], k)
             self.assertEqual(PathInfo(k).is_tag, self._testpaths[k][2], k)
             self.assertEqual(
                 PathInfo(k).is_entrypoint, self._testpaths[k][3], k)
Example #3
0
    def unlink(self, path):
        """
         * Standard file: standard behavior
         * Tagged file:
            - if path points to a file directly under the entry poiny,
              it completely deletes the file.
            - if path points to a file with one or more tags, remove
              from the file the last tag in the path.
        :param path:
        :return:
        """
        pathinfo = PathInfo(path)
        if pathinfo.is_tag or pathinfo.is_entrypoint:
            raise FuseOSError(errno.EISDIR)

        elif pathinfo.is_tagged_object:
            semfolder = self._get_semantic_folder(pathinfo.entrypoint)
            assert len(pathinfo.tagged_object) > 0

            if len(pathinfo.tags) == 0:
                # If it's directly under the entry point, delete it.
                os.unlink(self._datastore_path(path))
                semfolder.filetags.remove_file(pathinfo.tagged_object)
            else:
                # If it's a tagged path, remove the last tag.
                semfolder.filetags.discard_tag(pathinfo.tagged_object,
                                               pathinfo.tags[-1])

            self._save_semantic_folder(semfolder)

        else:
            os.unlink(self._datastore_path(path))
Example #4
0
    def readdir(self, path: str, fh):
        """
        Yelds the list of files and directories within the provided one.
        Already traversed tags of a semantic directory are not shown.
        :param path:
        :param fh:
        """
        dirents = []
        storepath = self._datastore_path(path)

        if os.path.isdir(storepath):
            pathinfo = PathInfo(path)
            if pathinfo.is_tag:
                folder = self._get_semantic_folder(pathinfo.entrypoint)
                # Show tags first
                dirents.extend(folder.graph.outgoing_arcs(pathinfo.tags[-1]))
                dirents.extend(folder.filetags.tagged_files(pathinfo.tags))
            elif pathinfo.is_entrypoint:
                dirents.extend(os.listdir(storepath))
            else:
                dirents.extend(os.listdir(storepath))

            # Remove reserved names and already traversed tags
            dirents = [
                x for x in dirents if not SemanticFS._is_reserved_name(x)
                and x not in pathinfo.tags
            ]

        for r in ['.', '..'] + dirents:
            yield r
Example #5
0
    def _move_standard_obj(self, old: PathInfo, new: PathInfo):
        """
        Helper method for renaming a standard file or folder.
        We can have the following cases:
         * Destination is a standard object:
           The source file is renamed just like the standard file system behavior
         * Destination is an entry point:
           The source object must be a directory.
           [TO BE COMPLETED]
         * Destination is a tag: not supported
         * Destination is a tagged object: [TO BE COMPLETED]
        :param old:
        :param new:
        """
        old_dspath = self._datastore_path(old.path)
        new_dspath = self._datastore_path(new.path)
        is_file = os.path.isfile(old_dspath)

        if new.is_standard_object:
            os.rename(old_dspath, new_dspath)
        elif new.is_entrypoint:
            if is_file:
                # Fail: trying to convert a file to an entry point
                raise FuseOSError(errno.ENOTSUP)
            else:
                # Convert src dir to an entry point

                # Fails if source dir contains an entry point
                for p in os.listdir(old_dspath):
                    if not self._is_reserved_name(
                            p) and PathInfo.is_semantic_name(p):
                        raise FuseOSError(errno.ENOTSUP)

                os.rename(old_dspath, new_dspath)
                semfolder = SemanticFolder(new.path)
                for f in os.listdir(new_dspath):
                    semfolder.filetags.add_file(f)
                self._save_semantic_folder(semfolder)
        elif new.is_tag:
            if is_file:
                # Fail: trying to convert a file to a tag
                raise FuseOSError(errno.ENOTSUP)
            else:
                # Convert src dir to a tag
                self._convert_folder_to_tag(old, new)
        elif new.is_tagged_object:
            # Move this obj to the destination entry point, then add the tags.
            semfolder = self._get_semantic_folder(new.entrypoint)
            os.rename(
                old_dspath,
                new_dspath)  # Fails if new is an existing non-empty directory
            try:
                semfolder.filetags.add_file(new.tagged_object, new.tags)
            except ValueError:
                semfolder.filetags.assign_tags(new.tagged_object, new.tags)
            self._save_semantic_folder(semfolder)
        else:
            # Impossible!
            assert False, "Impossible destination"
Example #6
0
    def _exists(self, path: str) -> bool:
        """
        Test whether the specified virtual path exists in the file system.
        :param path: a virtual path
        :return:
        """

        # Extracts from the path all the files that belong to a semantic directory in the path.
        # E.g., given the path "/a/_b/_c/d/e/_f/g/_h", we get
        # [ '/a/_b/_c/d',
        #   '/a/_b/_c/d/e/_f/g',
        #   '/a/_b/_c/d/e/_f/g/_h' (because it's the last one and it's semantic)
        # ]
        components = os.path.normcase(os.path.normpath(path)).split(os.sep)
        semantic_endpoints = []
        prev_was_semantic = False
        for i, name in enumerate(components):
            curr_is_semantic = PathInfo.is_semantic_name(name)
            if prev_was_semantic and not curr_is_semantic:
                semantic_endpoints.append(os.sep.join(components[0:i + 1]))
            prev_was_semantic = curr_is_semantic
        assert not PathInfo.is_semantic_name(components[-1]) or os.sep.join(
            components) not in semantic_endpoints
        if len(components) > 0 and PathInfo.is_semantic_name(components[-1]):
            semantic_endpoints.append(os.sep.join(components))

        for subpath in semantic_endpoints:
            pathinfo = PathInfo(subpath)
            assert pathinfo.is_tag or pathinfo.is_tagged_object or pathinfo.is_entrypoint

            try:
                folder = self._get_semantic_folder(pathinfo.entrypoint)
            except FileNotFoundError:
                return False

            if pathinfo.is_tagged_object and not folder.filetags.has_file(
                    pathinfo.tagged_object):
                return False
            if not folder.graph.has_path(pathinfo.tags):
                return False
            if pathinfo.is_tagged_object and not folder.filetags.has_tags(
                    pathinfo.tagged_object, pathinfo.tags):
                return False

        return os.path.lexists(self._datastore_path(path))
Example #7
0
    def _datastore_path(self, virtualpath: str) -> str:
        """
        Returns the path (of another file system) where the provided virtual object is actually stored.
        For example:
         * /a/_b/_c/x -> dsroot/a/_b/x
         * /a/_b/_c/ -> dsroot/a/_b/_c/
         * /a/_b/_c/_d/ -> dsroot/a/_b/_d/
        :param virtualpath: an absolute virtual path
        :return:
        """

        # NB: Using a 1-1 mapping for file names, we inherit the limitations of the underlying fs (e.g.
        # special file names, unallowed characters, case sensitivity, etc. In addition, this fs will
        # behave differently depending on the file system on which it's run.

        if not os.path.isabs(virtualpath):
            raise ValueError("virtualpath should be absolute")

        components = os.path.normcase(os.path.normpath(virtualpath)).split(
            os.sep)
        tmppath = []
        for i, name in enumerate(components):
            if i == 0 or i == 1:
                tmppath.append(name)
            else:
                if PathInfo.is_semantic_name(
                        tmppath[-2]) and PathInfo.is_semantic_name(
                            tmppath[-1]):
                    # _a/_b/_c => _a/_c
                    # _a/_b/x => _a/x
                    del tmppath[-1]
                tmppath.append(name)

        tmppath = os.sep.join(tmppath)

        # Remove the root from the path
        tmppath = os.path.splitdrive(tmppath)[1]
        if tmppath.startswith(os.sep):
            tmppath = tmppath[len(os.sep):]

        # Join the path with the datastore path
        path = os.path.join(self._dsroot, tmppath)

        return path
Example #8
0
    def release(self, path, fh):
        logger.debug("close(%s, %d)", path, fh)
        if fh in self._sem_write_descriptors:
            assert self._has_ghost_file(path) and PathInfo(
                path).is_tagged_object
            self._get_ghost_file(path).apply(fh)
            self._delete_ghost_file(path)
            self._sem_write_descriptors.remove(fh)

        return os.close(fh)
Example #9
0
    def symlink(self, name, target):
        logger.debug("symlink(%s, %s)", name, target)

        target_norm = os.path.normcase(os.path.normpath(target))
        pathinfo_name = PathInfo(name)

        if pathinfo_name.is_standard_object:
            return os.symlink(target, self._datastore_path(name))

        elif not os.path.isabs(target_norm) and len(target_norm.split(os.sep)) == 1 and \
                pathinfo_name.is_tag and target_norm == pathinfo_name.tags[-1]:

            # ln -s /_sem/_c /_sem/_a/_b/_c
            if self._exists(os.path.join(pathinfo_name.entrypoint,
                                         target_norm)):
                # Add the link to the tag
                if len(pathinfo_name.tags) >= 2:
                    semfolder = self._get_semantic_folder(
                        pathinfo_name.entrypoint)
                    semfolder.graph.add_arc(pathinfo_name.tags[-2],
                                            pathinfo_name.tags[-1])
                    self._save_semantic_folder(semfolder)
            else:
                raise FuseOSError(errno.ENOENT)

        elif not os.path.isabs(target_norm) and len(target_norm.split(os.sep)) == 1 and \
                pathinfo_name.is_tagged_object and target_norm == pathinfo_name.tagged_object:

            # Add the tags to the tagged object
            if self._exists(os.path.join(pathinfo_name.entrypoint,
                                         target_norm)):
                semfolder = self._get_semantic_folder(pathinfo_name.entrypoint)
                assert semfolder.filetags.has_file(pathinfo_name.tagged_object)
                semfolder.filetags.assign_tags(pathinfo_name.tagged_object,
                                               pathinfo_name.tags)
                self._save_semantic_folder(semfolder)
            else:
                raise FuseOSError(errno.ENOENT)

        elif pathinfo_name.is_tagged_object:
            semfolder = self._get_semantic_folder(pathinfo_name.entrypoint)
            if semfolder.filetags.has_file(pathinfo_name.tagged_object):
                raise FuseOSError(errno.EEXIST)
            else:
                os.symlink(target, self._datastore_path(name))
                semfolder.filetags.add_file(pathinfo_name.tagged_object,
                                            pathinfo_name.tags)
            self._save_semantic_folder(semfolder)

        else:
            raise FuseOSError(errno.ENOTSUP)
Example #10
0
    def open(self, path, flags):
        dspath = self._datastore_path(path)
        f = os.open(dspath, flags)
        logger.debug("open(%s, %s) -> %d", path,
                     SemanticFS._stringify_open_flags(flags), f)

        if flags & (os.O_WRONLY | os.O_RDWR) != 0:
            pathinfo = PathInfo(path)
            if pathinfo.is_tagged_object:
                assert f not in self._sem_write_descriptors
                self._sem_write_descriptors.add(f)
                self._add_ghost_file(path)

        return f
Example #11
0
    def _convert_folder_to_tag(self, old: PathInfo, new: PathInfo):
        if not (old.is_tagged_object or old.is_standard_object):
            raise ValueError(
                "Can only convert tagged object or standard object.")

        if not new.is_tag:
            raise ValueError(
                "Tagged object or standard object can only be converted to tag."
            )

        old_dspath = self._datastore_path(old.path)

        # Fails if source dir contains an entry point
        for p in os.listdir(old_dspath):
            if not self._is_reserved_name(p) and PathInfo.is_semantic_name(p):
                raise FuseOSError(errno.ENOTSUP)

        srcfiles = os.listdir(old_dspath)
        semfolder = SemanticFolder(new.entrypoint)
        if set(srcfiles) & set(semfolder.filetags.files()):
            # Name conflict: fails
            raise FuseOSError(errno.ENOTSUP)
        else:
            semfolder = self._get_semantic_folder(new.entrypoint)
            dir_mode = os.lstat(old_dspath).st_mode & 0o777
            self.mkdir(
                new.path,
                dir_mode)  # FIXME Avoid calling mkdir... do this internally

            if not semfolder.graph.has_node(new.tags[-1]):
                semfolder.graph.add_node(new.tags[-1])
            if len(new.tags) > 1:
                semfolder.graph.add_arc(new.tags[-2], new.tags[-1])

            entrypoint_dspath = self._datastore_path(new.entrypoint)
            for f in srcfiles:
                file_dspath = os.path.join(old_dspath, f)
                if os.path.isfile(file_dspath):
                    shutil.copy2(file_dspath, entrypoint_dspath)
                else:
                    shutil.copytree(file_dspath,
                                    os.path.join(entrypoint_dspath, f))
                semfolder.filetags.add_file(f, new.tags)

            self._save_semantic_folder(semfolder)

            self.rmdir(
                old.path)  # FIXME Avoid calling rmdir... do this internally
Example #12
0
    def mknod(self, path, mode, dev):
        logger.debug("mknod(%s)", path)

        pathinfo = PathInfo(path)
        dspath = self._datastore_path(path)
        os.mknod(dspath, mode, dev)

        # Files starting with the semantic prefix are not allowed
        if not (pathinfo.is_tagged_object or pathinfo.is_standard_object):
            raise FuseOSError(errno.ENOTSUP)

        if pathinfo.is_tagged_object:
            semfolder = self._get_semantic_folder(pathinfo.entrypoint)
            if semfolder.filetags.has_file(pathinfo.tagged_object):
                semfolder.filetags.assign_tags(pathinfo.tagged_object,
                                               pathinfo.tags)
            else:
                semfolder.filetags.add_file(pathinfo.tagged_object,
                                            pathinfo.tags)
            self._save_semantic_folder(semfolder)
Example #13
0
    def create(self, path, mode, fi=None):
        """
         * Standard file: standard behavior
         * Tagged file: create the file directly under the entry point, and add
           the appropriate tags. If the file already exists under the entry point,
           just add the tags.
        :param path:
        :param mode:
        :param fi:
        :return: write descriptor for the file
        """
        pathinfo = PathInfo(path)
        dspath = self._datastore_path(path)
        f = os.open(dspath, os.O_WRONLY | os.O_CREAT, mode)
        logger.debug(
            "create(%s, %s) -> %d", path,
            SemanticFS._stringify_open_flags(os.O_WRONLY | os.O_CREAT), f)

        # Files starting with the semantic prefix are not allowed
        if not (pathinfo.is_tagged_object or pathinfo.is_standard_object):
            raise FuseOSError(errno.ENOTSUP)

        if pathinfo.is_tagged_object:

            assert f not in self._sem_write_descriptors
            self._sem_write_descriptors.add(f)
            self._add_ghost_file(path).truncate(0)

            semfolder = self._get_semantic_folder(pathinfo.entrypoint)
            if semfolder.filetags.has_file(pathinfo.tagged_object):
                semfolder.filetags.assign_tags(pathinfo.tagged_object,
                                               pathinfo.tags)
            else:
                semfolder.filetags.add_file(pathinfo.tagged_object,
                                            pathinfo.tags)
            self._save_semantic_folder(semfolder)

            assert self._has_ghost_file(path)

        return f
Example #14
0
    def _add_ghost_file(self, ghost_path: str) -> GhostFile:
        """
        Adds a ghost file for the specified virtual path.
        If a ghost file already exists for that path, it doesn't add another one but keeps track
        of this additional reference (see `SemanticFS._delete_ghost_file`).
        :param ghost_path: the virtual path for the ghost file
        :return: the added GhostFile
        """
        dspath = self._datastore_path(ghost_path)
        normpath = os.path.normcase(os.path.normpath(ghost_path))

        if (dspath, normpath) in self._sem_writing_files:
            assert self._sem_writing_files_count[dspath, normpath] > 0
            self._sem_writing_files_count[dspath, normpath] += 1
        else:
            assert (dspath, normpath) not in self._sem_writing_files_count
            self._sem_writing_files[dspath, normpath] = GhostFile(
                dspath, lambda: self._clear_stat_cache(PathInfo(ghost_path)))
            self._sem_writing_files_count[dspath, normpath] = 1

        assert (dspath, normpath
                ) in self._sem_writing_files and self._sem_writing_files_count[
                    dspath, normpath] > 0
        return self._sem_writing_files[dspath, normpath]
Example #15
0
    def mkdir(self, path, mode):
        """
         * Standard directory: standard behavior
         * Entry point: creates the specified directory and adds the
           necessary metadata.
         * Tag:
            - if path points to a tag directly under the entry point, it adds the folder
              to the entry point and adds the relative node to the graph. Fails if the
              tag did exist.
            - if path points to a tag contained within another tag, it adds a link in the
              graph from the containing tag to the new one (if the tag that is being
              added didn't already exist within the semantic directory, it first adds the
              node to the graph and the tag folder to the entry point).
              Fails if the specified tag (associated to this semantic folder) is already
              present within the destination path. In other words, a tag can't be added
              if it has already been traversed.
         * Tagged folder: create the folder directly under the entry point, and add
              the appropriate tags. If the folder already exists under the entry point,
              just add the tags.
        :param path:
        :param mode:
        :raise FuseOSError:
        """
        pathinfo = PathInfo(path)
        if pathinfo.is_tag:
            # Creating a new tag
            if pathinfo.tags[-1] in pathinfo.tags[0:-1]:
                raise FuseOSError(errno.EEXIST)

            logger.debug("Creating tag: %s", path)
            semfolder = self._get_semantic_folder(pathinfo.entrypoint)

            if not semfolder.graph.has_node(pathinfo.tags[-1]):
                # Create the tag dir in the entry point's root
                os.mkdir(self._datastore_path(path), mode)
                semfolder.graph.add_node(pathinfo.tags[-1])

            if len(pathinfo.tags) >= 2:
                semfolder.graph.add_arc(pathinfo.tags[-2], pathinfo.tags[-1])

            self._save_semantic_folder(semfolder)

        elif pathinfo.is_entrypoint:
            # Creating a new entry point
            logger.debug("Creating entry point: %s", path)
            os.mkdir(self._datastore_path(path), mode)
            self._save_semantic_folder(SemanticFolder(path))

        elif pathinfo.is_tagged_object:
            # Adding a standard folder to a semantic directory
            logger.debug("Adding standard folder to semantic dir: %s", path)
            semfolder = self._get_semantic_folder(pathinfo.entrypoint)
            if semfolder.filetags.has_file(pathinfo.tagged_object):
                semfolder.filetags.assign_tags(pathinfo.tagged_object,
                                               pathinfo.tags)
            else:
                os.mkdir(self._datastore_path(path), mode)
                semfolder.filetags.add_file(pathinfo.tagged_object,
                                            pathinfo.tags)
            self._save_semantic_folder(semfolder)

        else:
            # No semantic parts... do a normal mkdir
            os.mkdir(self._datastore_path(path), mode)
Example #16
0
    def rmdir(self, path):
        """
         * Standard directory: standard behavior
         * Entry point: standard behavior
         * Tag:
            - if path points to a tag directly under the entry point,
              it completely deletes the tag. Fails if tag is not empty.
            - if path points to a tag contained within another tag,
              it removes the corresponding link in the graph. Doesn't
              fail if the tag is not empty.
         * Tagged folder:
            - if path points to a folder directly under the entry point,
              it completely deletes the folder. Fails if the folder is not
              empty, as would do the standard os call.
            - if path points to a folder with one or more tags, remove
              from the folder the last tag in the path. Doesn't fail if
              the folder is not empty.
        :param path:
        :return:
        """
        pathinfo = PathInfo(path)
        if pathinfo.is_tag:
            semfolder = self._get_semantic_folder(pathinfo.entrypoint)
            self._rmdir_tag(pathinfo, semfolder)
            self._save_semantic_folder(semfolder)

        elif pathinfo.is_entrypoint:
            dspath = self._datastore_path(path)

            # Even if the dir is logically empty, we can't remove it from the datastore because it contains
            # some special files. So first we make sure that the dir is empty from the user point-of-view, then
            # we unlink the special files, and at last we remove the directory.
            files = os.listdir(dspath)
            fsfiles = [
                SemanticFS.SEMANTIC_FS_GRAPH_FILE_NAME,
                SemanticFS.SEMANTIC_FS_ASSOC_FILE_NAME
            ]
            if set(files).issubset(fsfiles):
                for f in fsfiles:
                    os.unlink(os.path.join(dspath, f))
                os.rmdir(dspath)
            else:
                raise FuseOSError(errno.ENOTEMPTY)

        elif pathinfo.is_tagged_object:
            semfolder = self._get_semantic_folder(pathinfo.entrypoint)
            assert len(pathinfo.tagged_object) > 0

            if len(pathinfo.tags) == 0:
                # If it's directly under the entry point, delete it.
                os.rmdir(self._datastore_path(
                    path))  # Raises error if dir is not empty
                semfolder.filetags.remove_file(pathinfo.tagged_object)
            else:
                # If it's a tagged path, remove the last tag.
                semfolder.filetags.discard_tag(pathinfo.tagged_object,
                                               pathinfo.tags[-1])

            self._save_semantic_folder(semfolder)

        else:
            os.rmdir(self._datastore_path(path))
Example #17
0
    def _move_tagged_obj(self, old: PathInfo, new: PathInfo):
        """
        Helper method for renaming a tagged file or folder.
        :param old:
        :param new:
        """
        old_dspath = self._datastore_path(old.path)
        new_dspath = self._datastore_path(new.path)
        is_file = os.path.isfile(old_dspath)
        same_semantic_space = old.entrypoint == new.entrypoint

        if new.is_standard_object:
            # Remove the object from src and put it outside
            self._extract_tagged_object(old, new)

        elif new.is_entrypoint:
            if is_file:
                # Fail: trying to convert a file to an entry point
                raise FuseOSError(errno.ENOTSUP)
            else:
                # Convert src dir to an entry point

                # Fails if source dir contains an entry point
                for p in os.listdir(old_dspath):
                    if not self._is_reserved_name(
                            p) and PathInfo.is_semantic_name(p):
                        raise FuseOSError(errno.ENOTSUP)

                self._extract_tagged_object(old, new)
                semfolder = SemanticFolder(new.entrypoint)
                for f in os.listdir(new_dspath):
                    semfolder.filetags.add_file(f)
                self._save_semantic_folder(semfolder)

        elif new.is_tag:
            if is_file:
                # Fail: trying to convert a file to a tag
                raise FuseOSError(errno.ENOTSUP)
            else:
                # Convert src dir to a tag
                self._convert_folder_to_tag(old, new)

        elif new.is_tagged_object:
            if same_semantic_space:
                # Moving over itself. This case should have already been prevented by FUSE!
                assert not (old.tagged_object == new.tagged_object
                            and set(old.tags) == set(new.tags))

                if old.tagged_object != new.tagged_object and set(
                        old.tags) == set(new.tags):

                    # These cases:
                    #  * mv /_sem/_t1/x /_sem/_t1/y
                    #  * mv /_sem/x /_sem/y

                    # Rename the file in the root and in filestagsassociations
                    assert old.tagged_object != "" and new.tagged_object != ""
                    os.rename(old_dspath, new_dspath)
                    semfolder = self._get_semantic_folder(new.entrypoint)
                    semfolder.filetags.rename_file(old.tagged_object,
                                                   new.tagged_object)
                    self._save_semantic_folder(semfolder)

                elif old.tagged_object != new.tagged_object and set(
                        old.tags) != set(new.tags):

                    # mv /_sem/_t1/x /_sem/_t2/y is not supported
                    raise FuseOSError(errno.ENOTSUP)

                elif old.tagged_object == new.tagged_object and set(
                        old.tags) != set(new.tags):

                    # These cases:
                    #  * mv /_sem/_t1/x /_sem/_t2/x
                    #  * mv /_sem/x /_sem/_t3/x
                    semfolder = self._get_semantic_folder(new.entrypoint)
                    if len(old.tags) > 0:
                        semfolder.filetags.discard_tag(old.tagged_object,
                                                       old.tags[-1])
                    semfolder.filetags.assign_tags(new.tagged_object, new.tags)
                    self._save_semantic_folder(semfolder)

            else:
                self._extract_tagged_object(old, new)
                semfolder = self._get_semantic_folder(new.entrypoint)
                semfolder.filetags.add_file(new.tagged_object, new.tags)
                self._save_semantic_folder(semfolder)

        else:
            # Impossible!
            assert False, "Impossible destination"