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).
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?
CodePudding user response:
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