Search code examples
clinked-listscanf

Problems with fscanf skipping lines when assigning strings to a linked list from a txt file in C


The following code reads the input from a file called "automobili.txt" and outputs them into a different file, however I cannot figure out why the code skips over some strings when scanning the input

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

#define MAX_REG 8+1
#define MAX_OSG 2+1
#define MAX_DIO 5+1

typedef struct automobili {
    char registracija[MAX_REG];
    char osiguran[MAX_OSG];
    char dio[MAX_DIO];
    double steta;
    struct automobili *sledeci;
} AUTO;

//opens the txt files
FILE *open(char *ime, char *tip){
    FILE *fp = fopen(ime, tip);
    
    if (fp == NULL) {
        if (strcmp(tip, "r") == 0) {
            printf("Nije moguće otvoriti ulaznu datoteku %s!\n", ime);
            exit(1);
        } else if (strcmp(tip, "w") == 0) {
            printf("Nije moguće otvoriti izlaznu datoteku %s!\n", ime);
            exit(2);
        }
    }
    
    return fp;
}

//creates new list element
AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta){
    AUTO *novi = (AUTO*)malloc(sizeof(AUTO));
    
    if (novi == NULL) {
        printf("Greška prilikom zauzimanja memorije!\n");
        exit(EXIT_FAILURE);
    }
    
    strcpy(novi->registracija, registracija);
    strcpy(novi->osiguran, osiguran);
    strcpy(novi->dio, dio);
    novi->steta = steta;
    
    novi->sledeci = NULL;
    
    return novi;
}

//adds element to the end of the linked list
void dodaj_cvor(AUTO **glava, AUTO *novi){
    if (*glava == NULL) {
        *glava = novi;
        return;
    } else {
        AUTO *tekuci = *glava;
        while (tekuci->sledeci != NULL) {
            tekuci = tekuci->sledeci;
        }
        
        tekuci->sledeci = novi;
    }
}

//reads from the input file and adds new elements to the list using functions
void unos(FILE *in, AUTO **glava){
    char registracija[MAX_REG];
    char osiguran[MAX_OSG];
    char dio[MAX_DIO];
    double steta;
    
    while (fscanf(in, "%s %s %s %lf", registracija, osiguran, dio, &steta) != EOF) {
        AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
        dodaj_cvor(glava, novi);
    }
}

void inicijalizacija(AUTO **glava){
    *glava = NULL;
}

//deletes the list
void brisanje(AUTO **glava){
    if (*glava != NULL) {
        brisanje(&((*glava)->sledeci));
        free(*glava);
        *glava = NULL;
    }
}

void provjera_osiguranja(AUTO *glava) {
    while (glava != NULL) {
        if (strcmp(glava->osiguran, "da") == 0) {
            glava->steta = glava->steta * 0.85;
        }       
        glava = glava->sledeci;
    }
}

//checks for the right elemets and writres them down in the output file
void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]){
    double sum=0;
    while (glava != NULL) {
        if (strcmp(glava->registracija, br_reg) == 0) {
            fprintf(out, "%s %s %s %.2lf\n", glava->registracija, glava->osiguran, glava->dio, glava->steta);
            sum += glava->steta;
        }
        glava = glava->sledeci;
    }
    
    if (sum == 0) {
        printf("Registracija nije na spisku!\n");
        exit(11);
    }
    
    fprintf(out, "Ukupna vrijednost štete  je %.2lf din.\n", sum);
}

int main(int argc, char **argv){
    
    char br_reg[MAX_REG];
    
    //expected input is "./a.out automobili.txt <exit_filename>.txt"
    if (argc != 3) {
        printf("Loš unos! Pokušajte ponovo!\n");
        exit(EXIT_FAILURE);
    }
    
    FILE *in = open(argv[1], "r");
    FILE *out = open(argv[2], "w");
    
    AUTO *glava;    
    inicijalizacija(&glava);
    unos(in, &glava);
    
    printf("Unesite registraciju: ");
    fgets(br_reg, MAX_REG, stdin);
    
    provjera_osiguranja(glava);
    provjera_registracije(out, glava, br_reg);
    
    brisanje(&glava);
    fclose(in);
    fclose(out);
    
    return 0;
}

The input file:

PA454-TS da menjac 5500
BG777-OS da volan 11500
BG777-OS ne branik 7000
BG777-OS ne menjac 23500
PA454-TS da gume 16000
BG777-OS da kvacilo 33000

P.S. I apologize for non English code, the important functions have been commented on what each of them does.

The expected output when not checking for a specific registration number should have been:

PA454-TS da menjac 4675.00
BG777-OS da volan 9775.00
BG777-OS ne branik 7000.00
BG777-OS ne menjac 23500.00
PA454-TS da gume 13600.00
BG777-OS da kvacilo 28050.00
Ukupna vrijednost štete  je 86600.00 din.

However the program outputs:

 da menjac 4675.00
BG777-OS da volan 9775.00
 ne branik 7000.00
 ne menjac 23500.00
PA454-TS da gume 13600.00
o da kvacilo 28050.00
Ukupna vrijednost štete  je 86600.00 din.

I have tried rewriting the code however nothing changed and I cannot find a difference in how I executed the input here as compared to my previous code which works fine. I suspect the issue comes from fscanf() and not assigning the string to the element itself.


Solution

  • There are multiple problems in the code:

    while (fscanf(in, "%s %s %s %lf", registracija, osiguran, dio, &steta) != EOF) is very brittle and unsafe:

    • if any of the fields parsed for the %s conversions exceeds the length of the corresponding target array, this will cause a buffer overflow with undefined behavior.

    • as a matter of fact, the array dio has a length of 5+1 bytes and every other line in the input file has a longer string for this field, causing undefined behavior. You should specify the maximum length in the format string as %8s %2s %5s %lf and should probably increase these lengths to accommodate the actual file contents.

    • comparing the return value of fscanf() to EOF will only detect an unexpected end of file occurring before any field can be converted. You should instead test that the return value is 4, signifying all 4 conversions have succeeded.

    • calling fscanf() directly makes it difficult to detect invalid lines in the input file. It is recommended to use fgets() to read a line from the file and sscanf() to attempt the conversion, producing an informative error message upon failure.

    • open as a function or variable name should be avoided as this name refers to a system call on POSIX systems. Using open_file is both safer and more readable.

    • also note that macro definitions such as #define MAX_REG 8+1 are risky: if you write MAX_REG*2 in the code, it will expand to 8+1*2 which has the value 10 instead of the expected 18. Always parenthesize macro expressions as well as macro arguments used in the macro expression.

    Here is a modified version:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_REG (8+1)
    #define MAX_OSG (2+1)
    #define MAX_DIO (8+1)
    
    typedef struct automobili {
        char registracija[MAX_REG];
        char osiguran[MAX_OSG];
        char dio[MAX_DIO];
        double steta;
        struct automobili *sledeci;
    } AUTO;
    
    //opens the txt files
    FILE *open_file(const char *ime, const char *tip) {
        FILE *fp = fopen(ime, tip);
        if (fp == NULL) {
            if (strcmp(tip, "r") == 0) {
                printf("Nije moguće otvoriti ulaznu datoteku %s!\n", ime);
                exit(1);
            } else
            if (strcmp(tip, "w") == 0) {
                printf("Nije moguće otvoriti izlaznu datoteku %s!\n", ime);
                exit(2);
            } else {
                printf("Nije moguće otvoriti datoteku %s za mod %s!\n", ime, tip);
                exit(3);
            }
        }
        return fp;
    }
    
    // returns the length of the string if no truncation.
    // otherwise truncate the string and return the destination size.
    // destination is always null terminated if size > 0
    size_t pstrcpy(char *dest, size_t size, const char *src) {
        //size_t len = strnlen(src, size);
        size_t len;
        for (len = 0; len < size && src[len]; len++)
            continue;
        if (len < size)
            size = len + 1;
        if (size > 0) {
            memmove(dest, src, size - 1);
            dest[size - 1] = '\0';
        }
        return len;
    }
    
    //creates new list element
    AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
        AUTO *novi = (AUTO*)malloc(sizeof(AUTO));
    
        if (novi == NULL) {
            printf("Greška prilikom zauzimanja memorije!\n");
            exit(EXIT_FAILURE);
        }
    
        pstrcpy(novi->registracija, sizeof novi->registracija, registracija);
        pstrcpy(novi->osiguran, sizeof novi->osiguran, osiguran);
        pstrcpy(novi->dio, sizeof novi->dio, dio);
        novi->steta = steta;
        novi->sledeci = NULL;
        return novi;
    }
    
    //adds element to the end of the linked list
    void dodaj_cvor(AUTO **glava, AUTO *novi) {
        if (*glava == NULL) {
            *glava = novi;
            return;
        } else {
            AUTO *tekuci = *glava;
            while (tekuci->sledeci != NULL) {
                tekuci = tekuci->sledeci;
            }
            tekuci->sledeci = novi;
        }
    }
    
    //reads from the input file and adds new elements to the list using functions
    void unos(FILE *in, AUTO **glava) {
        char registracija[MAX_REG];
        char osiguran[MAX_OSG];
        char dio[MAX_DIO];
        double steta;
        char buf[100];
        char c;
    
        while (fgets(buf, sizeof buf, in)) {
            if (sscanf(buf, "%8s %2s %8s %lf %c", registracija, osiguran, dio, &steta, &c) == 4) {
                AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
                dodaj_cvor(glava, novi);
            } else {
                printf("invalid input line: %s\n", buf);
            }
        }
    }
    
    void inicijalizacija(AUTO **glava) {
        *glava = NULL;
    }
    
    //deletes the list
    void brisanje(AUTO **glava) {
        while (*glava != NULL) {
            AUTO *p = *glava;
            *glava = p->sledeci;
            free(p);
        }
    }
    
    void provjera_osiguranja(AUTO *glava) {
        while (glava != NULL) {
            if (strcmp(glava->osiguran, "da") == 0) {
                glava->steta = glava->steta * 0.85;
            }
            glava = glava->sledeci;
        }
    }
    
    //checks for the right elemets and writres them down in the output file
    void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
        double sum = 0;
        while (glava != NULL) {
            if (strcmp(glava->registracija, br_reg) == 0) {
                fprintf(out, "%s %s %s %.2lf\n", glava->registracija, glava->osiguran, glava->dio, glava->steta);
                sum += glava->steta;
            }
            glava = glava->sledeci;
        }
    
        if (sum == 0) {
            printf("Registracija nije na spisku!\n");
            exit(11);
        }
    
        fprintf(out, "Ukupna vrijednost štete  je %.2lf din.\n", sum);
    }
    
    int main(int argc, char **argv) {
    
        char br_reg[MAX_REG];
    
        //expected input is "./a.out automobili.txt <exit_filename>.txt"
        if (argc != 3) {
            printf("Loš unos! Pokušajte ponovo!\n");
            exit(EXIT_FAILURE);
        }
    
        FILE *in = open_file(argv[1], "r");
        FILE *out = open_file(argv[2], "w");
    
        AUTO *glava;
        inicijalizacija(&glava);
        unos(in, &glava);
    
        printf("Unesite registraciju: ");
        fgets(br_reg, MAX_REG, stdin);
    
        provjera_osiguranja(glava);
        provjera_registracije(out, glava, br_reg);
    
        brisanje(&glava);
        fclose(in);
        fclose(out);
    
        return 0;
    }