Python errno.ENOTEMPTY Examples

The following are 30 code examples of errno.ENOTEMPTY(). You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may also want to check out all available functions/classes of the module errno , or try the search function .
Example #1
Source File: file.py    From gnocchi with Apache License 2.0 6 votes vote down vote up
def _delete_measures_files_for_metric(self, metric_id, files):
        for f in files:
            try:
                os.unlink(self._build_measure_path(metric_id, f))
            except OSError as e:
                # Another process deleted it in the meantime, no prob'
                if e.errno != errno.ENOENT:
                    raise
        try:
            os.rmdir(self._build_measure_path(metric_id))
        except OSError as e:
            # ENOENT: ok, it has been removed at almost the same time
            #         by another process
            # ENOTEMPTY: ok, someone pushed measure in the meantime,
            #            we'll delete the measures and directory later
            # EEXIST: some systems use this instead of ENOTEMPTY
            if e.errno not in (errno.ENOENT, errno.ENOTEMPTY, errno.EEXIST):
                raise 
Example #2
Source File: model.py    From autokey with GNU General Public License v3.0 6 votes vote down vote up
def remove_data(self):
        if self.path is not None:
            for child in self.items:
                child.remove_data()
            for child in self.folders:
                child.remove_data()
            try:
                # The json file must be removed first. Otherwise the rmdir will fail.
                if os.path.exists(self.get_json_path()):
                    os.remove(self.get_json_path())
                os.rmdir(self.path)
            except OSError as err:
                # There may be user data in the removed directory. Only swallow the error, if it is caused by
                # residing user data. Other errors should propagate.
                if err.errno != errno.ENOTEMPTY:
                    raise 
Example #3
Source File: resource.py    From toil with Apache License 2.0 6 votes vote down vote up
def download(self, callback=None):
        """
        Downloads this resource from its URL to a file on the local system. This method should
        only be invoked on a worker node after the node was setup for accessing resources via
        prepareSystem().
        """
        dirPath = self.localDirPath
        if not os.path.exists(dirPath):
            tempDirPath = mkdtemp(dir=os.path.dirname(dirPath), prefix=self.contentHash + "-")
            self._save(tempDirPath)
            if callback is not None:
                callback(tempDirPath)
            try:
                os.rename(tempDirPath, dirPath)
            except OSError as e:
                # If dirPath already exists & is non-empty either ENOTEMPTY or EEXIST will be raised
                if e.errno == errno.ENOTEMPTY or e.errno == errno.EEXIST:
                    # Another process beat us to it.
                    # TODO: This is correct but inefficient since multiple processes download the resource redundantly
                    pass
                else:
                    raise 
Example #4
Source File: systemTest.py    From toil with Apache License 2.0 6 votes vote down vote up
def _testAtomicityOfNonEmptyDirectoryRenamesTask(parent, child, _):
    tmpChildDir = tempfile.mkdtemp(dir=parent, prefix='child', suffix='.tmp')
    grandChild = os.path.join(tmpChildDir, 'grandChild')
    open(grandChild, 'w').close()
    grandChildId = os.stat(grandChild).st_ino
    try:
        os.rename(tmpChildDir, child)
    except OSError as e:
        if e.errno == errno.ENOTEMPTY or e.errno == errno.EEXIST:
            os.unlink(grandChild)
            os.rmdir(tmpChildDir)
            return None
        else:
            raise
    else:
        # We won the race
        return grandChildId 
Example #5
Source File: ARFuncs.py    From ARSDKBuildUtils with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def ARDeleteRecursivelyNonMatching(path, regex=[]):
    if not os.path.isdir(path):
        ARDeleteFileIfNonMatching(path, regex=regex)
    else:
        for tst in os.listdir(path):
            ARDeleteRecursivelyNonMatching(os.path.join(path, tst), regex=regex)
        try:
            os.rmdir(path)
        except OSError as e:
            if e.errno != errno.ENOTEMPTY:
                raise e

# Gets the number of available CPUs
# If the real number can not be determined, return 1 
Example #6
Source File: exceptions.py    From smbprotocol with MIT License 5 votes vote down vote up
def __init__(self, ntstatus, filename, filename2=None):
        self.ntstatus = ntstatus
        self.filename2 = to_native(filename2) if filename2 else None

        ntstatus_name = 'STATUS_UNKNOWN'
        for name, val in vars(NtStatus).items():
            if ntstatus == val:
                ntstatus_name = name
                break

        error_details = {
            NtStatus.STATUS_OBJECT_NAME_NOT_FOUND: errno.ENOENT,
            NtStatus.STATUS_OBJECT_PATH_NOT_FOUND: errno.ENOENT,
            NtStatus.STATUS_OBJECT_NAME_COLLISION: errno.EEXIST,
            NtStatus.STATUS_PRIVILEGE_NOT_HELD: (errno.EACCES, "Required privilege not held"),
            NtStatus.STATUS_SHARING_VIOLATION: (errno.EPERM, "The process cannot access the file because it is being "
                                                             "used by another process"),
            NtStatus.STATUS_NOT_A_REPARSE_POINT: (errno.EINVAL, "The file or directory is not a reparse point"),
            NtStatus.STATUS_FILE_IS_A_DIRECTORY: errno.EISDIR,
            NtStatus.STATUS_NOT_A_DIRECTORY: errno.ENOTDIR,
            NtStatus.STATUS_DIRECTORY_NOT_EMPTY: errno.ENOTEMPTY,
            NtStatus.STATUS_END_OF_FILE: getattr(errno, 'ENODATA', 120),  # Not present on py2 for Windows.
        }.get(ntstatus, (0, "Unknown NtStatus error returned '%s'" % ntstatus_name))

        if not isinstance(error_details, tuple):
            error_details = (error_details, os.strerror(error_details))

        super(SMBOSError, self).__init__(error_details[0], error_details[1], to_native(filename)) 
Example #7
Source File: state.py    From blockade with Apache License 2.0 5 votes vote down vote up
def _state_delete(self):
        '''Try to delete the state.yml file and the folder .blockade'''
        try:
            os.remove(self._state_file)
        except OSError as err:
            if err.errno not in (errno.EPERM, errno.ENOENT):
                raise

        try:
            os.rmdir(self._state_dir)
        except OSError as err:
            if err.errno not in (errno.ENOTEMPTY, errno.ENOENT):
                raise 
Example #8
Source File: access_common.py    From yotta with Apache License 2.0 5 votes vote down vote up
def unpackFromCache(cache_key, to_directory):
    ''' If the specified cache key exists, unpack the tarball into the
        specified directory, otherwise raise NotInCache (a KeyError subclass).
    '''
    if cache_key is None:
        raise NotInCache('"None" is never in cache')

    cache_key = _encodeCacheKey(cache_key)

    cache_dir = folders.cacheDirectory()
    fsutils.mkDirP(cache_dir)
    path = os.path.join(cache_dir, cache_key)
    logger.debug('attempt to unpack from cache %s -> %s', path, to_directory)
    try:
        unpackFrom(path, to_directory)
        try:
            shutil.copy(path + '.json', os.path.join(to_directory, '.yotta_origin.json'))
        except IOError as e:
            if e.errno == errno.ENOENT:
                pass
            else:
                raise
        cache_logger.debug('unpacked %s from cache into %s', cache_key, to_directory)
        return
    except IOError as e:
        if e.errno == errno.ENOENT:
            cache_logger.debug('%s not in cache', cache_key)
            raise NotInCache('not in cache')
    except OSError as e:
        if e.errno == errno.ENOTEMPTY:
            logger.error('directory %s was not empty: probably simultaneous invocation of yotta! It is likely that downloaded sources are corrupted.')
        else:
            raise 
Example #9
Source File: dedupfs.py    From tgcloud with Apache License 2.0 5 votes vote down vote up
def __remove(self, path, check_empty=False):  # {{{3
        node_id, inode = self.__path2keys(path)
        # Make sure directories are empty before deleting them to avoid orphaned inodes.
        query = """ SELECT COUNT(t.id) FROM tree t, inodes i WHERE
                t.parent_id = ? AND i.inode = t.inode AND i.nlinks > 0 """
        if check_empty and self.__fetchval(query, node_id) > 0:
            raise OSError, (errno.ENOTEMPTY, os.strerror(errno.ENOTEMPTY), path)
        self.__cache_set(path, None)
        self.conn.execute('DELETE FROM tree WHERE id = ?', (node_id,))
        self.conn.execute('UPDATE inodes SET nlinks = nlinks - 1 WHERE inode = ?', (inode,))
        # Inodes with nlinks = 0 are purged periodically from __collect_garbage() so
        # we don't have to do that here.
        if self.__fetchval('SELECT mode FROM inodes where inode = ?', inode) & stat.S_IFDIR:
            parent_id, parent_ino = self.__path2keys(os.path.split(path)[0])
            self.conn.execute('UPDATE inodes SET nlinks = nlinks - 1 WHERE inode = ?', (parent_ino,)) 
Example #10
Source File: tempchecker.py    From conifer with Apache License 2.0 5 votes vote down vote up
def remove_empty_user_dir(self, warc_dir):
        # just in case, only remove empty  dir if it hasn't changed in a bit
        if (time.time() - os.path.getmtime(warc_dir)) < self.USER_DIR_IDLE_TIME:
            return False

        try:
            os.rmdir(warc_dir)
            logger.debug('TempChecker: Removed Empty User Dir: ' + warc_dir)
            return True
        except OSError as e:
            if e.errno not in [errno.ENOENT, errno.ENOTEMPTY]:
                logger.error(str(e))
            return False 
Example #11
Source File: helper.py    From trains with Apache License 2.0 5 votes vote down vote up
def delete_object(self, obj, **_):
        """
        Delete an object.

        :type obj: :class:`Object`
        :param obj: Object instance.

        :return: True on success.
        """

        path = self.get_object_cdn_url(obj)

        try:
            os.unlink(path)
        except Exception:
            return False

        # # Check and delete all the empty parent folders
        # path = os.path.dirname(path)
        # container_url = obj.container.get_cdn_url()
        #
        # # Delete the empty parent folders till the container's level
        # while path != container_url:
        #     try:
        #         os.rmdir(path)
        #     except OSError:
        #         exp = sys.exc_info()[1]
        #         if exp.errno == errno.ENOTEMPTY:
        #             break
        #         raise exp
        #
        #     path = os.path.dirname(path)

        return True 
Example #12
Source File: install.py    From Splunking-Crime with GNU Affero General Public License v3.0 5 votes vote down vote up
def warn_failed_remove(function, path, exc_info):
    if exc_info[1].errno == errno.EACCES:
        log.warn("Cannot remove, permission denied: {0}".format(path))
    elif exc_info[1].errno == errno.ENOTEMPTY:
        log.warn("Cannot remove, not empty: {0}".format(path))
    else:
        log.warn("Cannot remove, unknown reason: {0}".format(path)) 
Example #13
Source File: objectstore.py    From osbuild with Apache License 2.0 5 votes vote down vote up
def store_tree(self, destination: str):
        """Store the tree at destination and reset itself

        Moves the tree atomically by using rename(2). If the
        target already exist, does nothing. Afterwards it
        resets itself and can be used as if it was new.
        """
        self._check_writable()
        self._check_readers()
        self._check_writer()
        self.init()
        with ctx.suppress_oserror(errno.ENOTEMPTY, errno.EEXIST):
            os.rename(self._tree, destination)
        self.reset() 
Example #14
Source File: os_ext.py    From reframe with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def rmtree(*args, max_retries=3, **kwargs):
    '''Persistent version of ``shutil.rmtree()``.

    If ``shutil.rmtree()`` fails with ``ENOTEMPTY`` or ``EBUSY``, retry up to
    ``max_retries`times to delete the directory.

    This version of ``rmtree()`` is mostly provided to work around a race
    condition between when ``sacct`` reports a job as completed and when the
    Slurm epilog runs. See https://github.com/eth-cscs/reframe/issues/291 for
    more information.
    Furthermore, it offers a work around for nfs file systems where a ``.nfs*``
    file may be present during the ``rmtree()`` call which throws a busy
    device/resource error. See https://github.com/eth-cscs/reframe/issues/712
    for more information.

    ``args`` and ``kwargs`` are passed through to ``shutil.rmtree()``.

    If ``onerror``  is specified in  ``kwargs`` and is not  :class:`None`, this
    function is completely equivalent to ``shutil.rmtree()``.
    '''
    if 'onerror' in kwargs and kwargs['onerror'] is not None:
        shutil.rmtree(*args, **kwargs)
        return

    for i in range(max_retries):
        try:
            shutil.rmtree(*args, **kwargs)
            return
        except OSError as e:
            if i == max_retries:
                raise
            elif e.errno in {errno.ENOTEMPTY, errno.EBUSY}:
                pass
            else:
                raise 
Example #15
Source File: file_util_test.py    From google-apputils with Apache License 2.0 5 votes vote down vote up
def testRmDirsForNotEmptyDirectory(self):
    self.mox.StubOutWithMock(os, 'rmdir')
    os.rmdir('path/to').AndRaise(
        OSError(errno.ENOTEMPTY, 'Directory not empty', 'path/to'))

    self.mox.StubOutWithMock(shutil, 'rmtree')
    shutil.rmtree('path/to/directory')

    self.mox.ReplayAll()

    file_util.RmDirs('path/to/directory')
    self.mox.VerifyAll() 
Example #16
Source File: core.py    From cider with MIT License 5 votes vote down vote up
def unlink(self, name):
        symlinks = self.read_bootstrap().get("symlinks", {})

        removed_targets = set()
        found = False
        for source_glob, target in symlinks.items():
            if self._islinkkey(source_glob, name):
                found = True
                for source, target in self.expandtargets(source_glob, target):
                    self._remove_link_target(source, target)
                    removed_targets.add(target)
                    shutil.move(source, target)
                    print(tty.progress("Moved {0} -> {1}".format(
                        collapseuser(source),
                        collapseuser(target)
                    )))

        if not found:
            raise StowError("No symlink found with name: {0}".format(name))

        try:
            os.rmdir(os.path.join(self.symlink_dir, name))
        except OSError as e:
            if e.errno != errno.ENOTEMPTY:
                raise e

        self.remove_symlink(name)
        self._update_target_cache(
            set(self._cached_targets()) - removed_targets
        ) 
Example #17
Source File: misc.py    From rules_pip with MIT License 5 votes vote down vote up
def ensure_dir(path):
    # type: (AnyStr) -> None
    """os.path.makedirs without EEXIST."""
    try:
        os.makedirs(path)
    except OSError as e:
        # Windows can raise spurious ENOTEMPTY errors. See #6426.
        if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
            raise 
Example #18
Source File: path.py    From click-configfile with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def removedirs_p(self):
        """ Like :meth:`removedirs`, but does not raise an exception if the
        directory is not empty or does not exist. """
        try:
            self.removedirs()
        except OSError:
            _, e, _ = sys.exc_info()
            if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
                raise
        return self

    # --- Modifying operations on files 
Example #19
Source File: path.py    From click-configfile with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def rmdir_p(self):
        """ Like :meth:`rmdir`, but does not raise an exception if the
        directory is not empty or does not exist. """
        try:
            self.rmdir()
        except OSError:
            _, e, _ = sys.exc_info()
            if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
                raise
        return self 
Example #20
Source File: test_workspace_fs.py    From parsec-cloud with GNU Affero General Public License v3.0 5 votes vote down vote up
def test_rmdir(alice_workspace):
    await alice_workspace.mkdir("/foz")
    await alice_workspace.rmdir("/foz")
    lst = await alice_workspace.listdir("/")
    assert lst == [FsPath("/foo")]

    with pytest.raises(OSError) as context:
        await alice_workspace.rmdir("/foo")
    assert context.value.errno == errno.ENOTEMPTY

    with pytest.raises(NotADirectoryError):
        await alice_workspace.rmdir("/foo/bar")

    with pytest.raises(PermissionError):
        await alice_workspace.rmdir("/") 
Example #21
Source File: misc.py    From pipenv with MIT License 5 votes vote down vote up
def ensure_dir(path):
    # type: (AnyStr) -> None
    """os.path.makedirs without EEXIST."""
    try:
        os.makedirs(path)
    except OSError as e:
        # Windows can raise spurious ENOTEMPTY errors. See #6426.
        if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
            raise 
Example #22
Source File: reflink.py    From qubes-core-admin with GNU Lesser General Public License v2.1 5 votes vote down vote up
def _remove_empty_dir(path):
    try:
        os.rmdir(path)
        _fsync_path(os.path.dirname(path))
        LOGGER.info('Removed empty directory: %s', path)
    except OSError as ex:
        if ex.errno not in (errno.ENOENT, errno.ENOTEMPTY):
            raise 
Example #23
Source File: misc.py    From pex with Apache License 2.0 5 votes vote down vote up
def ensure_dir(path):
    # type: (AnyStr) -> None
    """os.path.makedirs without EEXIST."""
    try:
        os.makedirs(path)
    except OSError as e:
        # Windows can raise spurious ENOTEMPTY errors. See #6426.
        if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
            raise 
Example #24
Source File: common.py    From pex with Apache License 2.0 5 votes vote down vote up
def finalize(self, source=None):
    """Rename `work_dir` to `target_dir` using `os.rename()`.

    :param str source: An optional source offset into the `work_dir`` to use for the atomic
                       update of `target_dir`. By default the whole `work_dir` is used.

    If a race is lost and `target_dir` already exists, the `target_dir` dir is left unchanged and
    the `work_dir` directory will simply be removed.
    """
    if self.is_finalized:
      return

    source = os.path.join(self._work_dir, source) if source else self._work_dir
    try:
      # Perform an atomic rename.
      #
      # Per the docs: https://docs.python.org/2.7/library/os.html#os.rename
      #
      #   The operation may fail on some Unix flavors if src and dst are on different filesystems.
      #   If successful, the renaming will be an atomic operation (this is a POSIX requirement).
      #
      # We have satisfied the single filesystem constraint by arranging the `work_dir` to be a
      # sibling of the `target_dir`.
      os.rename(source, self._target_dir)
    except OSError as e:
      if e.errno not in (errno.EEXIST, errno.ENOTEMPTY):
        raise e
    finally:
      self.cleanup() 
Example #25
Source File: test_common.py    From pex with Apache License 2.0 5 votes vote down vote up
def test_atomic_directory_finalize_enotempty():
  atomic_directory_finalize_test(errno.ENOTEMPTY) 
Example #26
Source File: import_export.py    From signac with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def _copy_to_job_workspace(src, job, copytree):
    dst = job.workspace()
    try:
        copytree(src, dst)
    except (IOError, OSError) as error:
        if error.errno in (errno.ENOTEMPTY, errno.EEXIST):
            raise DestinationExistsError(job)
        raise
    else:
        job._init()
    return dst 
Example #27
Source File: job.py    From signac with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def move(self, project):
        """Move this job to project.

        This function will attempt to move this instance of job from
        its original project to a different project.

        :param project:
            The project to move this job to.
        :type project:
            :py:class:`~.project.Project`
        :raises DestinationExistsError:
            If the job is already initialized in project.
        :raises RuntimeError:
            If the job is not initialized or the destination is on a different
            device.
        :raises OSError:
            When the move failed due unexpected file system issues.
        """
        dst = project.open_job(self.statepoint())
        _mkdir_p(project.workspace())
        try:
            os.replace(self.workspace(), dst.workspace())
        except OSError as error:
            if error.errno == errno.ENOENT:
                raise RuntimeError(
                    "Cannot move job '{}', because it is not initialized!".format(self))
            elif error.errno in (errno.EEXIST, errno.ENOTEMPTY, errno.EACCES):
                raise DestinationExistsError(dst)
            elif error.errno == errno.EXDEV:
                raise RuntimeError(
                    "Cannot move jobs across different devices (file systems).")
            else:
                raise error
        self.__dict__.update(dst.__dict__) 
Example #28
Source File: hooks.py    From py2deb with MIT License 5 votes vote down vote up
def remove_empty_directory(directory):
    """
    Remove a directory if it is empty.

    :param directory: The pathname of the directory (a string).
    """
    try:
        os.rmdir(directory)
    except OSError as e:
        if e.errno not in (errno.ENOTEMPTY, errno.ENOENT):
            raise 
Example #29
Source File: util.py    From mock with GNU General Public License v2.0 4 votes vote down vote up
def rmtree(path, selinux=False, exclude=()):
    """Version of shutil.rmtree that ignores no-such-file-or-directory errors,
       tries harder if it finds immutable files and supports excluding paths"""
    if os.path.islink(path):
        raise OSError("Cannot call rmtree on a symbolic link")
    try_again = True
    retries = 0
    failed_to_handle = False
    failed_filename = None
    if path in exclude:
        return
    while try_again:
        try_again = False
        try:
            names = os.listdir(path)
            for name in names:
                fullname = os.path.join(path, name)
                if fullname not in exclude:
                    try:
                        mode = os.lstat(fullname).st_mode
                    except OSError:
                        mode = 0
                    if stat.S_ISDIR(mode):
                        try:
                            rmtree(fullname, selinux=selinux, exclude=exclude)
                        except OSError as e:
                            if e.errno in (errno.EPERM, errno.EACCES, errno.EBUSY):
                                # we alrady tried handling this on lower level and failed,
                                # there's no point in trying again now
                                failed_to_handle = True
                            raise
                    else:
                        os.remove(fullname)
            os.rmdir(path)
        except OSError as e:
            if failed_to_handle:
                raise
            if e.errno == errno.ENOENT:  # no such file or directory
                pass
            elif exclude and e.errno == errno.ENOTEMPTY:  # there's something excluded left
                pass
            elif selinux and (e.errno == errno.EPERM or e.errno == errno.EACCES):
                try_again = True
                if failed_filename == e.filename:
                    raise
                failed_filename = e.filename
                os.system("chattr -R -i %s" % path)
            elif e.errno == errno.EBUSY:
                retries += 1
                if retries > 1:
                    raise
                try_again = True
                getLog().debug("retrying failed tree remove after sleeping a bit")
                time.sleep(2)
            else:
                raise 
Example #30
Source File: chroot.py    From cjworkbench with GNU Affero General Public License v3.0 4 votes vote down vote up
def _walk_and_delete_upper_files(
    chroot: Chroot, should_delete: Callable[[Path, Path], bool] = lambda root_path: True
) -> None:
    """
    Delete files and directories from `root` that are in `upper` but not `base`.

    Ignore files and directories for which `should_delete(root_path) == False`.
    """
    # lightweight recursion: (dirpath, scandir iterator) at each
    # level of nesting
    #
    # We scandir *chroot.upper*. This is where all the _changes_ are
    # recorded; so we want it to appear empty (except for maybe a few
    # empty directories that mirror directories in chroot.base).
    #
    # DO NOT edit chroot.upper directly: that gives undefined behavior.
    # Delete from chroot.root.
    stack: List[Tuple[Path, Iterator[os.DirEntry]]] = [
        (chroot.upper, os.scandir(str(chroot.upper)))
    ]

    while stack:
        cur_dir, cur_scandir = stack[-1]
        try:
            entry: os.DirEntry = next(cur_scandir)
        except StopIteration:
            stack.pop()
            if cur_dir != chroot.upper:  # we're done
                # Delete the directory itself, unless it's in base layer or
                # we didn't delete its children.
                relative_path = Path(cur_dir).relative_to(chroot.upper)
                base_path = chroot.base / relative_path
                root_path = chroot.root / relative_path
                if not base_path.exists() and should_delete(root_path):
                    try:
                        root_path.rmdir()
                    except OSError as err:
                        if err.errno == errno.ENOTEMPTY:
                            pass
                        else:
                            raise
            continue

        relative_path = Path(entry.path).relative_to(chroot.upper)
        root_path = chroot.root / relative_path
        if entry.is_dir(follow_symlinks=False):
            if root_path.is_mount():
                # Don't follow mountpoints. /root/.local/share/virtualenvs
                # is a mountpoint in dev mode, to cache installed Python
                # modules.
                continue
            stack.append((Path(entry.path), os.scandir(entry.path)))
        elif should_delete(root_path):
            root_path.unlink()