1

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?

1 Answers1

3

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: %s\n", 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
larsks
  • 277,717
  • 41
  • 399
  • 399
  • Somehow this doesn't seem to work for me. `cat /proc/self/loginuid` doesn't find the file: `cat: /proc/self/loginuid: No such file or directory`, nor does `echo $UID > /proc/self/loginuid` (exits with `bash: /proc/self/loginuid: No such file or directory`). Trying to `touch` the file doesn't work and `nano` tells me the directory is not writeable. Looking into the directory with `ls` doesn't list the file in question either. Did you install any packages or such that may have created the file or do you have an idea to create it manually? I'd like to avoid monkey-patching if possible. – Martin Meilinger Oct 25 '22 at 02:11
  • I'm hosting from Windows 10 with Docker Desktop and WSL2. Edit: Did you just delete your comment? I could swear I saw you asking about my host environment a moment ago. – Martin Meilinger Oct 25 '22 at 02:39
  • I did delete the comment, because I ended up turning it into an extension to the answer! – larsks Oct 25 '22 at 02:50