os.getlogin in Docker container throws FileNotFoundError

Question:

If executed inside a Docker container, os.getlogin() throws a FileNotFoundError: [Errno 2] No such file or directory.
I am aware that the Python Docs recommend using a different method, but it’s in some code that I can’t just change.
I am using ubuntu 22.04 and python 3.10.6 (both inside the Docker container).
I’m hosting from Windows 10 with Docker Desktop and WSL2.

Here’s a MWE:

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && 
    apt-get install -y 
        python3

Build and run it with:

docker build -t mwe .
docker run -it mwe

then execute the following inside the Docker container:

python3 -c "import os; print(os.getlogin())"

and it will throw the error:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory

Is there a good way to avoid this error?

Asked By: Martin Meilinger

||

Answers:

This is partially a duplicate of Python os.getlogin problem, but the answers there don’t really get to the root of the problem.

This is not a Python issue. We can reproduce the same behavior with a minimal C program:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    char *login = getlogin();
    if (login == NULL) {
        perror("getlogin");
        exit(1);
    }

    printf("login: %sn", login);
    exit(0);
}

If you run this in your container, you’ll get as output:

getlogin: No such device or address

Tracing this through the glibc source code, the error comes from the getlogin code, but is actually caused by this code in __getlogin_r_loginuid:

  if (uid == (uid_t) -1)
      {
            __set_errno (ENXIO);
                  return ENXIO;
      }

And we’re hitting that code because /proc/self/loginuid has the value:

$ cat /proc/self/loginuid
4294967295

Where 4294967295 is simply -1, which in this case means, "this value has not been initialized", and that’s because we haven’t entered this shell through any sort of login manager that would normally set that for us.

For a simple workaround, we can just write our current UID to that file:

root@d4da9e11f42e:~# python3 -c 'import os; print(os.getlogin())'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 6] No such device or address
root@d4da9e11f42e:~# echo $UID > /proc/self/loginuid
root@d4da9e11f42e:~# python3 -c 'import os; print(os.getlogin())'
root

That would be relatively easy to bake into your container startup.

Another option is monkeypatching the os module to replace os.getlogin with something else (such as getpass.getuser()).


It sounds as if WSL doesn’t provide the loginuid feature.

This answer still accurately identifies the problem: getlogin() fails to read the loginuid file, and you get the same error (you can see that code path earlier in the implementation of __getlogin_r_loginuid).

If your kernel doesn’t provide this feature, your only real option is fixing the code — either by modifying the calls to os.getlogin(), or by arranging to monkeypatch the os module before the legacy code calls os.getlogin().


One slightly less orthodox alternative is to use function interposition to override the getlogin library call.

Start with this minimal C code in fake_getlogin.c:

char *getlogin(void) {
    return "root";
}

Compiled it to a shared library:

gcc -fPIC -c fake_getlogin.c
ld -o fake_getlogin.so -shared fake_getlogin.o

Embed this in a Dockerfile and arrange to preload it via /etc/ld.so.preload:

FROM ubuntu:22.04

RUN DEBIAN_FRONTEND=noninteractive 
    apt-get update && 
    apt-get install -y 
        python3

COPY fake_getlogin.so /lib/fake_getlogin.so
RUN echo /lib/fake_getlogin.so > /etc/ld.so.preload

Now re-try your original reproducer and you should find that it runs without errors:

root@4c86cde47db1:/# python3 -c 'import os; print(os.getlogin())'
root

This assumes that there aren’t any more WSL-specific quirks to overcome.


If you were to decide to use the function interposition solution in practice, you should probably arrange to build the shared library as part of your image build process; e.g.:

FROM ubuntu:22.04 AS builder

RUN DEBIAN_FRONTEND=noninteractive 
    apt-get update && 
    apt-get install -y 
        build-essential

WORKDIR /src
COPY fake_getlogin.c ./
RUN gcc -fPIC -c fake_getlogin.c && 
    ld -o fake_getlogin.so -shared fake_getlogin.o

FROM ubuntu:22.04

COPY --from=builder /src/fake_getlogin.so /lib/fake_getlogin.so

RUN DEBIAN_FRONTEND=noninteractive 
    apt-get update && 
    apt-get install -y 
        python3

RUN echo /lib/fake_getlogin.so > /etc/ld.so.preload
Answered By: larsks
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.