python asyncio gets deadlock if multiple stdin input is needed

前端 未结 3 1661
旧时难觅i
旧时难觅i 2021-02-20 10:06

I wrote a command-line tool to execute git pull for multiple git repos using python asyncio. It works fine if all repos have ssh password-less login setup. It also

3条回答
  •  南方客
    南方客 (楼主)
    2021-02-20 11:04

    General speaking, the recommended way to feed password to git is through "credential helpers" or GIT_ASKPASS, as pointed out by the answer of Martijn, but for Git+SSH, the situation is complicated (more discussion below). So it'd be difficult to set this up correctly across OS. If you just want a quick patch to your script, here is the code that works in both Linux and Windows:

    async def run_async(...):
        ...
        process = await asyncio.create_subprocess_exec( *cmds, 
            stdin=asyncio.subprocess.PIPE, 
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE, 
            start_new_session=True, cwd=path)
        stdout, stderr = await process.communicate(password + b'\n')
    

    The parameter start_new_session=True will set a new SID to the child process so that it got assigned a new session which have no controlling TTY by default. Then SSH will be forced to read the password from the stdin pipe. On Windows, start_new_session seems to have no effect (there is no concept of SID on Windows AFAIK).

    Unless you plan to implement a Git-credential-manager (GCM) in your project "gita", I won't recommend to feed any password to Git at all (the unix philosophy). Simply set stdin=asyncio.subprocess.DEVNULL and pass None to process.communicate(). This will force Git and SSH to use the existing CM or abort (you can handle the error later). Moreover, I think "gita" doesn't want to mess up with the configuration of other CMs, such as GCM for windows. Thus, do not bother to touch the GIT_ASKPASS or SSH_ASKPASS variables, or any credential.* configuration. It's the user's responsibility (and freedom) to setup a proper GCM for each repo. Usually the Git distribution includes a GCM or an ASKPASS implementation already.

    Discussion

    There is a common misunderstanding to the problem: Git doesn't open the TTY for password input, SSH does! Actually, other ssh-related utilities, such as rsync and scp, share the same behavior (I figured this out the hard way when debugging a SELinux related problem a few months ago). See the appendix for verification.

    Because Git calls SSH as a sub-process, it cannot know whether SSH will open TTY or not. The Git configurables, such as core.askpass or GIT_ASKPASS, will not prevent SSH from opening /dev/tty, at least not for me when testing with Git 1.8.3 on CentOS 7 (detail in the appendix). There are two common cases that you should expect a password prompt:

    • Server requires password authentication;
    • For public-key authentication, the private key storage (in a local file ~/.ssh/id_rsa or PKCS11 chip) is password protected.

    In these cases, ASKPASS or GCM won't help you on the deadlock problem. You have to disable the TTY.

    You may also want to read about the environment variable SSH_ASKPASS. It points to an executable that will be called when the following conditions are met:

    • No controlling TTY is available to the current session;
    • Env. variable DISPLAY is set.

    On Windows, for example, it defaults to SSH_ASKPASS=/mingw64/libexec/git-core/git-gui--askpass. This program comes with the main-stream distribution and the official Git-GUI package. Therefore, on both Windows and Linux desktop environments, if you disable TTY by start_new_session=True and leave the other configurables unchanged, SSH will automatically popup a separate UI window for password prompt.

    Appendix

    To verify which process opens the TTY, you can run ps -fo pid,tty,cmd when a Git process is waiting for password.

    $ ps -fo pid,tty,cmd
    3839452 pts/0         \_ git clone ssh://username@hostname/path/to/repo ./repo
    3839453 pts/0             \_ ssh username@hostname git-upload-pack '/path/to/repo'
    
    $ ls -l /proc/3839453/fd /proc/3839452/fd
    /proc/3839452/fd:
    total 0
    lrwx------. 1 xxx xxx 64 Apr  4 21:45 0 -> /dev/pts/0
    lrwx------. 1 xxx xxx 64 Apr  4 21:45 1 -> /dev/pts/0
    lrwx------. 1 xxx xxx 64 Apr  4 21:43 2 -> /dev/pts/0
    l-wx------. 1 xxx xxx 64 Apr  4 21:45 4 -> pipe:[49095162]
    lr-x------. 1 xxx xxx 64 Apr  4 21:45 5 -> pipe:[49095163]
    
    /proc/3839453/fd:
    total 0
    lr-x------. 1 xxx xxx 64 Apr  4 21:42 0 -> pipe:[49095162]
    l-wx------. 1 xxx xxx 64 Apr  4 21:42 1 -> pipe:[49095163]
    lrwx------. 1 xxx xxx 64 Apr  4 21:42 2 -> /dev/pts/0
    lrwx------. 1 xxx xxx 64 Apr  4 21:42 3 -> socket:[49091282]
    lrwx------. 1 xxx xxx 64 Apr  4 21:45 4 -> /dev/tty
    

提交回复
热议问题