Search code examples
cmemory-leaksgetline

Memory leak with getline C


I am trying to parse a config file with getline but sometimes I have a memory leak and sometimes not.

#define _GNU_SOURCE
#include "parser.h"

#include <stdlib.h>
#include <string.h>

struct global parse_global(char *line, FILE *file)
{
    char *log_file = NULL;
    bool log = false;
    char *pid_file = NULL;

    size_t len;
    ssize_t nread;
    char *saveptr = NULL;

    while ((nread = getline(&line, &len, file)) != -1
           && strcmp(line, "[[vhosts]]\n") != 0)
    {
        char *arg_name = strtok_r(line, " =", &saveptr);
        char *arg_value = strtok_r(NULL, " =\n", &saveptr);

        if (strcmp(arg_name, "log_file") == 0)
        {
            log_file = strdup(arg_value);
        }
        else if (strcmp(arg_name, "log") == 0)
        {
            if (strcmp(arg_value, "true") == 0)
            {
                log = true;
            }
            else
            {
                log = false;
            }
        }
        else if (strcmp(arg_name, "pid_file") == 0)
        {
            pid_file = strdup(arg_value);
        }
    }

    return global_init(log_file, log, pid_file);
}

struct vhost parse_vhost(char *line, FILE *file)
{
    char *server_name = "my_server";
    char *port = NULL;
    char *ip = NULL;
    char *root_dir = NULL;
    char *default_file = NULL;

    size_t len;
    ssize_t nread;
    char *saveptr = NULL;

    while ((nread = getline(&line, &len, file)) != -1
           && strcmp(line, "\n") != 0)
    {
        char *arg_name = strtok_r(line, " =", &saveptr);
        char *arg_value = strtok_r(NULL, " =\n", &saveptr);

        if (strcmp(arg_name, "server_name") == 0)
        {
            server_name = strdup(arg_value);
        }
        else if (strcmp(arg_name, "port") == 0)
        {
            port = strdup(arg_value);
        }
        else if (strcmp(arg_name, "ip") == 0)
        {
            ip = strdup(arg_value);
        }
        else if (strcmp(arg_name, "root_dir") == 0)
        {
            root_dir = strdup(arg_value);
        }
        else if (strcmp(arg_name, "default_file") == 0)
        {
            default_file = strdup(arg_value);
        }
    }
    
    return vhost_init(server_name, port, ip, root_dir, default_file);
}

void free_global(struct global g)
{
    free(g.pid_file);
    free(g.log_file);
}

void free_vhost(struct vhost v)
{
    free(v.server_name);
    free(v.port);
    free(v.ip);
    free(v.root_dir);
    free(v.default_file);
}

struct server *fileconfig_parser(char *file)
{
    FILE *f = fopen(file, "r");
    if (!f)
    {
        perror("cannot open file");
        return NULL;
    }

    char *line = NULL;
    size_t len = 0;

    ssize_t nread = getline(&line, &len, f);
    if (nread == -1 || strcmp(line, "[global]\n") != 0)
    {
        free(line);
        perror("invalid file");
        fclose(f);
        return NULL;
    }

    struct global global = parse_global(line, f);
    if (global.pid_file == NULL)
    {
        free_global(global);
        fclose(f);
        free(line);
        return NULL;
    }

    struct vhost vhost = parse_vhost(line, f);
    if (vhost.server_name == NULL || vhost.port == NULL || vhost.ip == NULL
        || vhost.root_dir == NULL)
    {
        free_global(global);
        free_vhost(vhost);
        fclose(f);
        free(line);
        return NULL;
    }

    struct server *server = server_init(global, vhost);

    free(line);
    fclose(f);

    return server;
}

The memory come from the line allocated by getline but sometimes when I run the programme there is no memory leak and I dont know why. It seems to me that I free line in every cases.

I also don’t know why it happened randomly.

=================================================================
==1561==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 120 byte(s) in 1 object(s) allocated from:
    #0 0x7fad2918a808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x7fad28f0e543 in _IO_getdelim /build/glibc-SzIz7B/glibc-2.31/libio/iogetdelim.c:62
    #2 0x7ffde0365e1f  ([stack]+0x1ee1f)

SUMMARY: AddressSanitizer: 120 byte(s) leaked in 1 allocation(s).

Solution

  • According to the man docs,

    getline() reads an entire line from stream, storing the address of the buffer containing the text into *lineptr. The buffer is null-terminated and includes the newline character, if one was found.

    If *lineptr is set to NULL and *n is set 0 before the call, then getline() will allocate a buffer for storing the line. This buffer should be freed by the user program even if getline() failed.

    This means that you have to explicitly call free() even if you never directly malloc'ed.

    Refer to this example given in the man pages

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
            FILE *stream;
            char *line = NULL;
            size_t len = 0;
            ssize_t nread;
    
            if (argc != 2)
            {
                    fprintf(stderr, "Usage: %s <file>\n", argv[0]);
                    exit(EXIT_FAILURE);
            }
    
            stream = fopen(argv[1], "r");
            if (stream == NULL)
            {
                    perror("fopen");
                    exit(EXIT_FAILURE);
            }
    
            while ((nread = getline(&line, &len, stream)) != -1)
            {
                    printf("Retrieved line of length %zu:\n", nread);
                    fwrite(line, nread, 1, stdout);
            }
    
            free(line);
            fclose(stream);
            exit(EXIT_SUCCESS);
    }