Linux reboot function called in C program causes file loss created by the program on disk
Asked Answered
D

4

9

I have developped a C program (Linux), this program create a new file and write into, after that it reboots the PC.

After reboot, I have lost the file created by my program. When I deactivate reboot function, the file created by my program is still present.

This behaviour is seen with Linux: - OpenWrt (Backfire 10.03) on VirtualBox (filesystem ext2) - Linux (Ubuntu) (filesystem ext4)

Have you an explication for this behavior and how can I fix it?

#include <stdio.h>
#include <sys/reboot.h>

int main ()
{
    FILE    *pFile;
    char    mybuffer[80];

    pFile = fopen ("/home/user/Desktop/example.txt","w");
    if (pFile == NULL) perror ("Error opening file");
    else
    {
        fputs ("test",pFile);
        fclose (pFile);
    }
    rename("/home/user/Desktop/example.txt","/home/user/Desktop/example123.txt");
    reboot(RB_AUTOBOOT);
    return 0;
}
Doriadorian answered 14/5, 2012 at 14:56 Comment(4)
Flush (sync) the file yourself: linux.die.net/man/2/fsync...Spinach
I don't disagree, but I'm surprised that calling fclose() isn't sufficient.Supination
@Supination See my answers. The man page explicitly states this. Its a bit weird though.Endothecium
Huh. Now I've learned something. :)Supination
E
8

The man page for fclose says:

Note that fclose() only flushes the user space buffers provided by the C library. To ensure that the data is physically stored on disk the kernel buffers must be flushed too, for example, with sync(2) or fsync(2).

Which means that you need to call fsync before closing the file descriptor.

Endothecium answered 14/5, 2012 at 15:10 Comment(1)
Thank you for the explanation, now I understands where does the loss of data from my program.Doriadorian
E
9

The immediate problem is, that you don't sync the file before doing the reboot. The actual problem is, that you call the reboot syscall directly, without regard for what else is happening on the system. What you do is very similar to simply pressing the HW reset button; you just give the kernel the chance to do a little bit of cleanup, but then everything is killed the hard way. This is a dead sure way to eventually corrupt filesystems and file structures. Don't do this!.

Instead you should ask the init system to perform a gracefull reboot. Calling the reboot syscall requires privileged access. So you can just ask the init system to reboot as well. On most systems there's a symlink /sbin/reboot that points to the program that will initiate a sane reboot if called through that symlink. Hence I recommend you replace your dirty reboot(RB_AUTOBOOT) with (note the double specification of "/sbin/reboot" in execlp – this is important).

pid_t reboot_pid;
if( 0 == (reboot_pid = fork()) ) {
    execlp("/sbin/reboot", "/sbin/reboot", NULL);
    exit(1); /* never reached if execlp succeeds. */
}
if( -1 == reboot_pid ) {
    /* fork error... deal with it somehow */
}
int reboot_status;
waitpid(reboot_pid, &reboot_status, 0);
if( !WIFEXITED(reboot_status) ) {
    /* reboot process did not exit sanely... deal with it somehow */
}
if( 0 != WIFEXITSTATUS(reboot_status) ) {
    /* reboot process exited with error;
     * most likely the user lacks the required privileges */
}
else {
    fputs("reboot call sucessfull -- system is about to shutdown.");
    /* The init system is now shutting down the system. It will signals all
     * programs to terminate by sending SIGTERM, followed by SIGKILL to
     * programs that didn't terminate gracefully. */
}

Doing it that way the system can shut down gracefully, terminate all programs running in a clean way and unmount all filesystems before doing the reboot, thereby keeing filesystem and data integrity.

Note that if you expect your program not to have root access, then you'll have to jump some hoops; on systems with systemd you can send a reboot request by D-Bus. But except it to fail, if the user executing the command does not have reboot privileges.

Extinct answered 18/8, 2014 at 9:56 Comment(0)
E
8

The man page for fclose says:

Note that fclose() only flushes the user space buffers provided by the C library. To ensure that the data is physically stored on disk the kernel buffers must be flushed too, for example, with sync(2) or fsync(2).

Which means that you need to call fsync before closing the file descriptor.

Endothecium answered 14/5, 2012 at 15:10 Comment(1)
Thank you for the explanation, now I understands where does the loss of data from my program.Doriadorian
S
1

I think the important thing here is that the reboot never returns, so your program never really exits normally.

In normal conditions (i.e. a program that exits or even crashes after calling fclose), the file descriptors underlying your FILE * will get closed and their kernel buffers flushed.

In this case, however, since reboot never returns, I suspect that the kernel buffers aren't getting cleaned up in the usual way, and therefore stuff isn't getting written to disk because of it.

An fsync call will probably take care of it. If you want to be paranoid, do fsync, then use fileno() to get a file descriptor and use sync() to insure that the buffers are flushed. At that point there shouldn't be anything of the file left in the process address space, and your call to reboot shouldn't cause any more problems.

Smashing answered 14/5, 2012 at 15:20 Comment(0)
I
1

An alternate solution is to call sync as per the reboot man page

LINUX_REBOOT_CMD_POWER_OFF (RB_POWER_OFF, 0x4321fedc; since Linux 2.1.30). The message "Power down." is printed, the system is stopped, and all power is removed from the system, if possible. If not preceded by a sync(2), data will be lost.

Imbalance answered 14/10, 2015 at 19:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.