cronie-crond / cronie

Cronie cron daemon project
Other
481 stars 81 forks source link

anacron: Can't open /dev/null on file-descriptor 0: No such file or directory #156

Closed YoruStar closed 1 year ago

YoruStar commented 1 year ago

I found that the daily scheduled tasks in my system are no longer being executed. The error reported in the log is

anacron: Can't open /dev/null on file-descriptor 0: No such file or directory

This error is due to anacron shutting down stdin but failing, The relevant code is

if (fclose(stdin)) die_e("Can't close stdin");
xopen(STDIN_FILENO, "/dev/null", O_RDONLY);

The code snippet first calls fclose(stdin) to close the standard input stream. Normally, file descriptor 0 should be closed, and the return value of open('/dev/null', O_RDONLY) should be 0. However, in my exceptional environment, the return value is 4.

By tracing system calls with strace, it was also found that there was no system call to close(0) before the open call.

chdir("/var/spool/anacron")             = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
gettid()                                = 294830
readlink("/proc/294830/exe", "/usr/sbin/anacron", 100) = 17
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 4

I don't know the reason for this error, but I think closing standard input may be achieved using a different approach.

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

int main(void) { 
    int fd = open("/dev/null", O_WRONLY);
    dup2(fd, 0);
    close(fd);

    char c = getchar(); 
    printf("The character is %c\n", c); return 0; 
}

If could find out the reason why fclose(stdin) not call close(0), it would be better.

Thank you very much

t8m commented 1 year ago

Could you please try this patch:

diff --git a/anacron/main.c b/anacron/main.c
index 65f8fed..1ad904a 100644
--- a/anacron/main.c
+++ b/anacron/main.c
@@ -209,13 +209,13 @@ go_background(void)

     /* stdin is already closed */

-    if (fclose(stdout)) die_e("Can't close stdout");
+    xclose(STDOUT_FILENO);
     /* coverity[leaked_handle] – fd 1 closed automatically */
-    xopen(1, "/dev/null", O_WRONLY);
+    xopen(STDOUT_FILENO, "/dev/null", O_WRONLY);

-    if (fclose(stderr)) die_e("Can't close stderr");
+    xclose(STDERR_FILENO);
     /* coverity[leaked_handle] – fd 2 closed automatically */
-    xopen(2, "/dev/null", O_WRONLY);
+    xopen(STDERR_FILENO, "/dev/null", O_WRONLY);

     pid = xfork();
     if (pid != 0)
@@ -476,7 +476,7 @@ main(int argc, char *argv[])

     if (sigprocmask(0, NULL, &old_sigmask)) die_e("sigset error");

-    if (fclose(stdin)) die_e("Can't close stdin");
+    xclose(STDIN_FILENO);
     xopen(STDIN_FILENO, "/dev/null", O_RDONLY);

     if (!no_daemon && !testing_only)

I know you can use dup2() instead but this should help as well.

YoruStar commented 1 year ago

I know you can use dup2() instead but this should help as well.

Your suggested modification can indeed solve my problem, but would it be possible to use dup2() for an even better solution?

t8m commented 1 year ago

I do not think the dup2 solution is that much different.