简体   繁体   中英

testing the program for various memory allocation errors and memory leaks

The tee utility copies its standard input to both stdout and to a file. This allows the user to view the output of a command on the console while writing a log to a file at the same time.

My program implements the tee command from linux POSIX system calls, with the -a option.

How can I modify the program to test for possible memory allocation errors? Positive memory leaks. Also, the memory allocation doesn't seem right to me. When creating a new buffer each time I call getline() , should I declare and initialize line outside the loop and reallocate it only after the loop has ended?

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "apue.h"

int writeAll(int fd, char *buf, int buflen);

int main(int argc, char *argv[]) {
    struct stat status;
    int option;
    bool append = false;
    int errCode = 0;

    while ((option = getopt(argc, argv, "a")) != -1) {
        switch (option) {
          case 'a':
            append = true;
            break;
        }
    }

    // We need to write in all the files given as parameter AND stdout.
    int numFileDescriptors = argc - optind + 1;

    int *fileDescriptors = malloc((numFileDescriptors + 1) * sizeof(*fileDescriptors));
    char **fileNames = malloc((numFileDescriptors + 1) * sizeof(*fileNames));

    int lastFileDescriptor = 0;
    fileDescriptors[0] = STDOUT_FILENO;
    fileNames[0] = "stdout";

    int flags = O_CREAT | O_WRONLY;
    if (append) {
        flags = flags | O_APPEND;
    } else {
        flags = flags | O_TRUNC;
    }

    for (int i = optind; i < argc; i++) {
        if (access(argv[i], F_OK) == 0) {
            if (access(argv[i], W_OK) < 0) {
                err_msg("%s: Permission denied", argv[i]);
                errCode = 1;
                continue;
            }
        }

        if (lstat(argv[i], &status) < 0) {
            status.st_mode = 0;
        }

        if (S_ISDIR(status.st_mode)) {
            err_msg("%s: Is a directory", argv[i]);
            errCode = 1;
            continue;
        }

        int fd = open(argv[i], flags, 0644);
        if (fd < 0) {
            err_msg("%s: Failed to open", argv[i]);
            errCode = 1;
            continue;
        }

        lastFileDescriptor = lastFileDescriptor + 1;
        fileDescriptors[lastFileDescriptor] = fd;
        fileNames[lastFileDescriptor] = argv[i];
    }

    while (true) {
        size_t len = 0;
        ssize_t read = 0;
        char *line = NULL;

        read = getline(&line, &len, stdin);
        if (read == -1) {
            break;
        }

        for (int i = 0; i <= lastFileDescriptor; i++) {
            int written = writeAll(fileDescriptors[i], line, strlen(line));
            if (written < 0) {
                err_msg("%s: Failed to write", fileNames[i]);
                errCode = 1;
            }
        }
    }

    for (int i = 0; i <= lastFileDescriptor; i++) {
        close(fileDescriptors[i]);
    }

    free(fileDescriptors);
    free(fileNames);

    return errCode;
}

int writeAll(int fd, char *buf, int buflen) {
    ssize_t written = 0;

    while (written < buflen) {
        int writtenThisTime = write(fd, buf + written, buflen - written);
        if (writtenThisTime < 0) {
            return writtenThisTime;
        }

        written = written + writtenThisTime;
    }

    return written;
}

Testing for memory allocation failure is simple: just add tests, report the failure and exit with a non zero exit status.

To avoid memory leaks, you must free the line that was allocated by getline inside the while (true) loop:

    while (true) {
        size_t len = 0;
        char *line = NULL;
        ssize_t nread = getline(&line, &len, stdin);
        if (nread == -1) {
            if (errno == ENOMEM) {
                fprintf(stderr, "out of memory\n");
                exit(1);
            }
            free(line);
            break;
        }

        for (int i = 0; i <= lastFileDescriptor; i++) {
            int written = writeAll(fileDescriptors[i], line, nread);
            if (written < 0) {
                err_msg("%s: Failed to write", fileNames[i]);
                errCode = 1;
            }
        }
        free(line);
    }

Alternately, you can reuse the same line for the next iteration and only free the buffer after the while loop:

    size_t len = 0;
    char *line = NULL;
    while (true) {
        ssize_t nread = getline(&line, &len, stdin);
        if (nread == -1) {
            if (errno == ENOMEM) {
                fprintf(stderr, "out of memory\n");
                exit(1);
            }
            break;
        }
        for (int i = 0; i <= lastFileDescriptor; i++) {
            int written = writeAll(fileDescriptors[i], line, nread);
            if (written < 0) {
                err_msg("%s: Failed to write", fileNames[i]);
                errCode = 1;
            }
        }
    }
    free(line);

Note that reading a full line at a time is risky as the input might contain very long, possibly unlimited lines (eg: /dev/zero ). You might want to use fgets() to read a line with a limited length and dispatch the contents as you read, possibly splitting long lines:

    char line[4096];
    while (fgets(line, sizeof line, stdin)) {
        size_t len = strlen(line);
        for (int i = 0; i <= lastFileDescriptor; i++) {
            int written = writeAll(fileDescriptors[i], line, len);
            if (written < 0) {
                err_msg("%s: Failed to write", fileNames[i]);
                errCode = 1;
            }
        }
    }

The above code has a limitation: if the input streams contains null bytes, they will cause some data to be lost in translation. A solution is to not use fgets() , but getchar() directly:

    for (;;) {
        char line[4096];
        size_t len = 0;
        int c;
        while (len < sizeof(line) && (c = getchar()) != EOF)) {
            if ((line[len++] = c) == '\n')
                break;
        }
        if (len > 0) {
            for (int i = 0; i <= lastFileDescriptor; i++) {
                int written = writeAll(fileDescriptors[i], line, len);
                if (written < 0) {
                    err_msg("%s: Failed to write", fileNames[i]);
                    errCode = 1;
                }
            }
        }
        if (c == EOF)
            break;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM