is it a good practice to close file descriptors on exit [closed]
Asked Answered
O

7

34

If for some reason, I discover a fatal situation in my program, and I would like to exit with an error code. Sometimes, the context of the fatal error is outside the scope of other file-descriptors.

is it a good practice to close these file descriptors?

As far as I know, these files are automatically closed when the process dies.

Osterman answered 6/3, 2013 at 12:1 Comment(5)
Design your system in such a way, that there is always the possibility to perform a clean shut-down. The latter includes freeing/unlocking any resources still in use by the system that is about to go down.Gentlewoman
As import this rightly says explicit is always better than implicit!!Wunder
@Gentlewoman This is a complicated legacy system, cascading errors is not possible, and refactoring is not an option.Osterman
C only guarantees that all files are closed if you exit either via the exit function, or a return from main.Extravasation
@Extravasation so if process halts because of a SIGABRT some descriptor could still be considered "in use" by the OS?Serena
B
19

Files are automatically closed, but it's a good practice.

See valgrind on this example

david@debian:~$ cat demo.c
#include <stdio.h>

int main(void)
{
    FILE *f;

    f = fopen("demo.c", "r");
    return 0;
}
david@debian:~$ valgrind ./demo
==3959== Memcheck, a memory error detector
==3959== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==3959== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==3959== Command: ./demo
==3959== 
==3959== 
==3959== HEAP SUMMARY:
==3959==     in use at exit: 568 bytes in 1 blocks
==3959==   total heap usage: 1 allocs, 0 frees, 568 bytes allocated
==3959== 
==3959== LEAK SUMMARY:
==3959==    definitely lost: 0 bytes in 0 blocks
==3959==    indirectly lost: 0 bytes in 0 blocks
==3959==      possibly lost: 0 bytes in 0 blocks
==3959==    still reachable: 568 bytes in 1 blocks
==3959==         suppressed: 0 bytes in 0 blocks
==3959== Rerun with --leak-check=full to see details of leaked memory
==3959== 
==3959== For counts of detected and suppressed errors, rerun with: -v
==3959== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)

As you can see, it raises a memory leak

On some circumstances you can make use of atexit():

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

static FILE *f;

static void free_all(void)
{
    fclose(f);
}

static int check(void)
{
    return 0;
}

int main(void)
{
    atexit(free_all);
    f = fopen("demo.c", "r");
    if (!check()) exit(EXIT_FAILURE);
    /* more code */
    return 0;
}
Blacktop answered 6/3, 2013 at 12:6 Comment(1)
FILE pointer returned by fopen() is not a file descriptor. It is a file stream, which has an associated buffer. So when your program returns from main without proper fclose() call you leave this memory leaked, as valgrind shows. The question about the file descriptors remains unanswered.Gobbet
G
22

The classic guide to POSIX programming "Advanced programming in UNIX environment" states:

When a process terminates, all of its open files are closed automatically by the kernel. Many programs take advantage of this fact and don't explicitly close open files.

You did not mention the OS in your question but such behavior should be expected from any OS. Whenever your program control flow crosses exit() or return from main() it is the system responsibility to clean up after the process.

There is always a danger of bugs in OS implementation. But, on the other hand, system has to deallocate way more than several open file descriptors at the process termination: memory occupied by the executable file image, stack, kernel objects associated with the process. You can not control this behavior from the user space, you just rely on its working-as-intended. So, why can't a programmer rely on the automatic close of the fds?

So, the only problem with leaving fds open may be the programming style question. And, as in the case of using stdio objects (i.e. stuff built around the system-provided file i/o), you may get (somewhat) disorienting alerts while valgrinding. As for the danger of leaking system resources, there should be nothing to worry about, unless your OS implementation is really buggy.

Gobbet answered 6/3, 2013 at 13:9 Comment(0)
B
19

Files are automatically closed, but it's a good practice.

See valgrind on this example

david@debian:~$ cat demo.c
#include <stdio.h>

int main(void)
{
    FILE *f;

    f = fopen("demo.c", "r");
    return 0;
}
david@debian:~$ valgrind ./demo
==3959== Memcheck, a memory error detector
==3959== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==3959== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==3959== Command: ./demo
==3959== 
==3959== 
==3959== HEAP SUMMARY:
==3959==     in use at exit: 568 bytes in 1 blocks
==3959==   total heap usage: 1 allocs, 0 frees, 568 bytes allocated
==3959== 
==3959== LEAK SUMMARY:
==3959==    definitely lost: 0 bytes in 0 blocks
==3959==    indirectly lost: 0 bytes in 0 blocks
==3959==      possibly lost: 0 bytes in 0 blocks
==3959==    still reachable: 568 bytes in 1 blocks
==3959==         suppressed: 0 bytes in 0 blocks
==3959== Rerun with --leak-check=full to see details of leaked memory
==3959== 
==3959== For counts of detected and suppressed errors, rerun with: -v
==3959== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)

As you can see, it raises a memory leak

On some circumstances you can make use of atexit():

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

static FILE *f;

static void free_all(void)
{
    fclose(f);
}

static int check(void)
{
    return 0;
}

int main(void)
{
    atexit(free_all);
    f = fopen("demo.c", "r");
    if (!check()) exit(EXIT_FAILURE);
    /* more code */
    return 0;
}
Blacktop answered 6/3, 2013 at 12:6 Comment(1)
FILE pointer returned by fopen() is not a file descriptor. It is a file stream, which has an associated buffer. So when your program returns from main without proper fclose() call you leave this memory leaked, as valgrind shows. The question about the file descriptors remains unanswered.Gobbet
S
8

As far as I know, these files are automatically closed when the process dies.

Don't rely on that. Conceptually, when the process dies, it is your responsibility to free allocated memory, close non-standard file descriptors, etc. Of course, every sane OS (and even Windows) will clean up after your process, but that's not something to expect.

Seedbed answered 6/3, 2013 at 12:4 Comment(0)
B
4

Yes. Suppose your main program is now a class in a separate program. Now you just described a resource leak. You're essentially violating encapsulation by relying on global program state, i.e. the state of the process - not your module, not a class, not an ADT, not a thread, but the whole process - being in a shutdown state.

Beloved answered 6/3, 2013 at 12:6 Comment(0)
S
4

Every sane operating system (certainly any form of Linux, or Windows) will close the files when the program terminates. If you have a very simple program then you probably don't need to close files on termination. However closing the files explicitly is still good practice, for the following reasons:

  1. if you leave it to the OS you have no control over the order in which the files are closed, which may lead to consistency problems (such as in a multi-file database).

  2. if there are errors associated with closing the file (such as I/O errors, out of space errors, etc) you have no way of reporting them.

  3. there may be interactions with file locking which need to be handled.

  4. a routine to close all files can handle any other clean-up that the program needs at the same time (flushing buffers, for instance)

Surroundings answered 2/5, 2017 at 8:43 Comment(0)
E
3

C does guarantee that all open files will be closed if your program terminates normally (i.e. via exit or a return from main). However, if your program terminates abnormally, e.g. it's closed by the operating system due to using a NULL pointer, it's up to the operating system to close the files. Therefore it's a good idea to make sure files are closed once they're no longer needed in case of unexpected termination.

The other reason is resource limits. Most operating systems have limits on the number of files open (as well as many other things), and so it's good practice to return those resources as soon as they're no longer needed. If every program kept all its files open indefinitely, systems could run into problems quite quickly.

Extravasation answered 6/3, 2013 at 13:16 Comment(0)
B
0

Well this piece of goofy code could be christened as fd bomb since it opens a current dir in a newly preempted process and does not exit completely so no post mortem resources de-allocation like closing fd is happening:

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char *bin = NULL;
char *_argv = NULL;
char *_envp = NULL;

void handler(void) {
    execve(bin, &_argv, &_envp);
}

int main (int argc, char **argv, char **envp) {
    bin = argv[0];
    _argv = *argv;
    _envp = *envp;

    printf("program name: %s pid: %d\n", argv[0], getpid());

    int fd = open(".", O_DIRECTORY);
    
    sleep(1);
    
    atexit(handler);

    return 0;
}

Tested in WSL2 with gcc as compiler.

Bremser answered 3/10, 2023 at 10:59 Comment(9)
Identifiers at file scope that start with _ are reserved: "All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces." The processing of argv and envp are also very suspect and has a bad code smell - the address of _argv does not refer to an array of pointers to strings that ends with a NULL pointer, thus the call to execve() invokes undefined behavior.Helmut
@AndrewHenle I accept all concerns stated above except one -- the piece of code has well defined behavior.Let's dissect itBremser
@AndrewHenle We have a program that basically does nothing, obtaining fd of an cwd, sleeping for 1 sec, registering atexit callback and bailing out gracefully. In turn atexit callback does execve which replaces caller process image in memory with a new one right before calling process should have exited so no resources de-allocation is happening w/using OS gears. Basically it is an recursion call at the level of ABI. So its pitfall I have expressed is well explained with this ^^^.Bremser
Anyway it is a foot shooter code which is just fun to experiment with but not to use in real projects. But the idea of self sustaining process w/o usage of supervisory services in the way of recursively calling yourself at exit sound interesting to me (as a dirty horny hack).Bremser
@AndrewHenle moreover **argv array is null terminated i.e. has null at argv[argc].Bremser
The argv array may be NULL terminated, but _argv is an "array" of just one element and it's guaranteed to not be NULL terminated. _argv contains the value of argv[0], which can't be NULL in your code because then bin would be NULL and then execve() would have a NULL first argument and immediately fail. Likewise, _envp contains the value of envp[0] which is almost certainly not NULL, so the single element "array" referred to by &_envp is also almost certainly guaranteed to not be NULL terminated.Helmut
You need to pass the exact values held in argv to execve(), not copy the value of argv[0] to another char * pointer and then pass the address of that pointer as a new char *argv[] to execve() - _argv is not an array. Or create an entirely new char *newArgv[] of a large enough size to hold the arguments you want and a NULL terminater and pass that to execve(). The same logic applies to envp.Helmut
This does not provide an answer to the question.Farina
@Farina yes, u right! but it clearly shows the way of exploitation -- so probably the obvious conclusion could be like "yes, close your fds as soon as possible before returning from main()" isn't!Bremser

© 2022 - 2024 — McMap. All rights reserved.