Running Certbot on Windows - fcntl error & how to fix

Here, I ran into this problem.

  File "c:\python37\Lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\python37\Lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\***\Envs\CertBot-Virtual\Scripts\certbot.exe\__main__.py", line 5, in <module>
  File "c:\users\***\envs\certbot-virtual\lib\site-packages\certbot\main.py", line 18, in <module>
    from certbot import account
  File "c:\users\***\envs\certbot-virtual\lib\site-packages\certbot\account.py", line 23, in <module>
    from certbot import util
  File "c:\users\***\envs\certbot-virtual\lib\site-packages\certbot\util.py", line 26, in <module>
    from certbot import lock
  File "c:\users\***\envs\certbot-virtual\lib\site-packages\certbot\lock.py", line 3, in <module>
    import fcntl
ModuleNotFoundError: No module named 'fcntl'

After searching for awhile, I found a fix.

Steps to fix

  1. pip install portalocker.
  2. Replace everything inside lock.py with:
import errno
import portalocker
import logging
import os

from certbot import errors

logger = logging.getLogger(__name__)

# This file has been edited as fcntl doesn't exist on windows, and several errors occur.


def lock_dir(dir_path):
    """Place a lock file on the directory at dir_path.

    The lock file is placed in the root of dir_path with the name
    .certbot.lock.

    :param str dir_path: path to directory

    :returns: the locked LockFile object
    :rtype: LockFile

    :raises errors.LockError: if unable to acquire the lock

    """
    return LockFile(os.path.join(dir_path, '.certbot.lock'))


class LockFile(object):
    """A UNIX lock file.

    This lock file is released when the locked file is closed or the
    process exits. It cannot be used to provide synchronization between
    threads. It is based on the lock_file package by Martin Horcicka.

    """
    def __init__(self, path):
        """Initialize and acquire the lock file.

        :param str path: path to the file to lock

        :raises errors.LockError: if unable to acquire the lock

        """
        super(LockFile, self).__init__()
        self._path = path
        self._file = None

        self.acquire()

    def acquire(self):
        """Acquire the lock file.

        :raises errors.LockError: if lock is already held
        :raises OSError: if unable to open or stat the lock file

        """
        while self._file is None:
            # Open the file
            file = open(self._path, 'a+')
            try:
                self._try_lock(file)
                if self._lock_success(file):
                    self._file = file
            finally:
                # Close the file if it is not the required one
                if self._file is None:
                    os.close(file)

    def _try_lock(self, file):
        """Try to acquire the lock file without blocking.

        :param int file: file descriptor of the opened file to lock

        """
        try:
            portalocker.lock(file, portalocker.LOCK_EX | portalocker.LOCK_NB)
        except IOError as err:
            if err.errno in (errno.EACCES, errno.EAGAIN):
                logger.debug(
                    "A lock on %s is held by another process.", self._path)
                raise errors.LockError(
                    "Another instance of Certbot is already running.")
            raise

    def _lock_success(self, file):
        """Did we successfully grab the lock?

        Because this class deletes the locked file when the lock is
        released, it is possible another process removed and recreated
        the file between us opening the file and acquiring the lock.

        :param int file: file descriptor of the opened file to lock

        :returns: True if the lock was successfully acquired
        :rtype: bool

        """
        try:
            stat1 = os.stat(self._path)
        except OSError as err:
            if err.errno == errno.ENOENT:
                return False
            raise

        stat2 = os.stat(file.name)
        # If our locked file descriptor and the file on disk refer to
        # the same device and inode, they're the same file.
        return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino

    def __repr__(self):
        repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path)
        if self._file is None:
            repr_str += 'released>'
        else:
            repr_str += 'acquired>'
        return repr_str

    def release(self):
        """Remove, close, and release the lock file."""
        # It is important the lock file is removed before it's released,
        # otherwise:
        #
        # process A: open lock file
        # process B: release lock file
        # process A: lock file
        # process A: check device and inode
        # process B: delete file
        # process C: open and lock a different file at the same path
        #
        # Calling os.remove on a file that's in use doesn't work on
        # Windows, but neither does locking with fcntl.
        try:
            self._file.close()
            os.remove(self._path)
        finally:
            try:
                self._file.close()
            finally:
                self._file = None
  1. You are done! Now you should get an AttributeError message (upon running certbot) as described in the tutorial, and should be able to follow the tutorial & fix it. There may be some more errors, but they should be easy to fix if you know Python a bit.

Tested on Python 3.7, Windows 10 (1803 Update).
Hope this helps someone!

6 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.