skip to Main Content

The following C program creates an empty file of a specified length. I have tested this on my local Ubuntu 20.04 laptop and it works. I am now running it in an instance at Digital Ocean (Ubuntu 22.04), but the line "fclose(outfile);" throws a segmentation fault. In the cloud I am running it in a Docker container, but not locally.

#include <stdio.h>
#include <string.h>
#include <errno.h>

size_t create_empty_file (char * out_fname, size_t file_size)
{
    FILE *outfile;
    char buf[1024];

    outfile = fopen(out_fname, "wb+");

    if (outfile == NULL) {
        strcpy(buf, strerror(errno));
        printf("%d --> %sn", errno, buf); }

    fseek(outfile, file_size, SEEK_SET);

    if (errno != 0) {
        strcpy(buf, strerror(errno));
        printf("%d --> %sn", errno, buf); }

    fputc('', outfile);

    if (errno != 0) {
        strcpy(buf, strerror(errno));
        printf("%d --> %sn", errno, buf); }

    fclose( outfile );

    return 0;
}

The file does get created AND the file does get closed — I know that because it shows a file size of zero until the file is closed, and the file does show its 4GB specified length.

In the usual case, fclose throws a segfault when the file pointer is null, but that’s not the case here. And it works on my local laptop.

Here are the segfault details from GDB:

Program received signal SIGSEGV, Segmentation fault.
0x00007fe121321c70 in __GI__IO_setb (f=f@entry=0x145b210, b=b@entry=0x0, eb=eb@entry=0x0, a=a@entry=0) at ./libio/genops.c:338
338 ./libio/genops.c: No such file or directory.

Thanks for any help with this.

2

Answers


  1. Chosen as BEST ANSWER

    Based on the suggestion by Shawn above, to use ftruncate, I rewrote this using open, ftruncate and close, and it works perfectly.

    size_t create_empty_file (char * out_fname, off_t file_size, mode_t mode) {

    char buf[1024];
    
    int fd = open(out_fname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    
    if (fd == 0) {
        strcpy(buf, strerror(errno));
        printf("%d --> %sn", errno, buf); }
        
    ftruncate(fd, file_size);
    
    if (errno != 0) {
        strcpy(buf, strerror(errno));
        printf("%d --> %sn", errno, buf); }
    
    close(fd);
    
    return 0;
    

    }


  2. You are not handling the errors correctly, and that may cause memory corruption, which breaks fclose(...). Most likely the bug is in your program, not in fclose(...).

    Here is how to handle the errors correctly:

    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    
    /* Returns 0 on success, -1 on error. Sets errno to a useless value. */
    int create_empty_file(char *out_fname, size_t file_size) {
        FILE *outfile = fopen(out_fname, "wb");
        if (outfile == NULL) {
            fprintf(stderr, "fopen: %d: %sn", errno, strerror(errno));
            return -1;
        }
        if (file_size != 0) {
            if (fseek(outfile, file_size - 1, SEEK_SET) != 0) {
                fprintf(stderr, "fopen: %d: %sn", errno, strerror(errno));
                fclose(outfile);
                return -1;
            }
            fputc('', outfile);  /* Error checked below. */
            if (ferror(outfile) || fflush(outfile) != 0) {
                fprintf(stderr, "fputcn");
                fclose(outfile);
                return -1;
            }
        }
        fclose(outfile);
        return 0;
    }
    

    After changing your function to this, you won’t get a segmentation fault. You may get an I/O error though (printed by fprintf(stderr, ...) above), pay attention to it. Maybe the reason for the I/O error is that your filesystem is full. (However, that’s unusual in this case, creating a huge file by seeking shouldn’t use much filesystem space, most filesystems optimize those holes away.)

    Here are some useful rules of thumb for error checking:

    • In general, check the return value of each function you call.

    • Only use errno if the return value of the previous I/O function was not success.

    • For stdio buffered output (e.g. fputc(...)), you may skip the error checking, and check the error after flushing explicitly (if (ferror(outfile) || fflush(outfile) != 0) in the code above).

    • In general, stop I/O on a file (and close it) after the first error.

    • Propagate the error indication in the return value of your function.

    • Print the error the stderr.

    I’ve fixed two more problems above:

    • + is not needed in fopen(..., "wb+"), because you don’t want to read from the file.
    • You need file_size - 1 for seeking, because fputc adds 1 more byte.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search