cbinaryfilesfseek

How to flag the last struct in a binary file as "DELETED" in binary files, C


I need to delete the last struct written in the file file.dat and flag it with the word deleted. This is the code:

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

struct s_cliente {
    int codice;
    char nome[10];
    char merce[10];
    float prezzo;
};

typedef struct s_cliente cliente;

cliente inserisciCliente(cliente c1);
void inserisciOrdine();
void stampaFile();
float calcolaPrezzo(char nome[10]);
void eliminaLast();

int main() {

    char nome[10];
    float prezzotot = 0;
    int opz = 0;
    
    do {
        printf("\nInserisci un'opzione: \n1)Inserisci un cliente\n2)Stampa i clienti all'interno del file\n3)Ricerca un cliente e calcola l'importo dovuto\n");
        scanf("%d", &opz);
        switch (opz) {
          case 1:
            inserisciOrdine();
            break;
          case 2:
            stampaFile();
            break;
          case 3:
            getchar();
            scanf("%s", nome);
            prezzotot = calcolaPrezzo(nome);
            if (prezzotot != 0) {
                printf("\nImporto dovuto dal cliente %s: %2.f euro", nome, prezzotot);
            } else {
                printf("\nCliente non trovato");
            }
            break;
          default:
            printf("\nOpzione non valida");
            break;
        }
    } while (opz != 5);    
    return 0;
}

cliente inserisciCliente(cliente c1) {
    printf("\nInserisci il codice: ");
    scanf("%d", &c1.codice);
    getchar();
    printf("Inserisci il nome: ");
    scanf("%s", c1.nome);
    getchar();
    printf("Inserisci la merce: ");
    scanf("%s", c1.merce);
    printf("Inserisci prezzo: ");
    scanf("%f", &c1.prezzo);

    return c1;
}

void inserisciOrdine() {
    FILE *foutput = fopen("file.dat", "ab");
    cliente c1;
    c1 = inserisciCliente(c1);
    fwrite(&c1, sizeof(c1), 1, foutput);
    fclose(foutput);
}

void stampaFile() {
    FILE *file_lettura = fopen("file.dat", "rb");
    if (file_lettura != NULL) {
        cliente c1;
        while (fread(&c1, sizeof(cliente), 1, file_lettura) == 1) {
            printf("Codice: %d, Nome: %s, Merce: %s, Prezzo: %.2f\n", c1.codice, c1.nome, c1.merce, c1.prezzo);
        }
        fclose(file_lettura);
    } else {
        printf("Errore nell'apertura del file per la lettura.\n");
    }
}

float calcolaPrezzo(char nome[10]) {
    float prezzotot = 0;
    FILE *file_lettura = fopen("file.dat", "rb");
    cliente c1;
    if (file_lettura != NULL) {
        while (fread(&c1, sizeof(cliente), 1, file_lettura) == 1) {
            if (strcmp(nome, c1.nome) == 0) {
                prezzotot += c1.prezzo;
            }
        }
        fclose(file_lettura);
    } else {
        printf("\nErrore nella lettura");
    }

    return prezzotot;
}

I know that I can use the function fseek();, I just don't really understand how to flag the struct as deleted.


Solution

  • Google translate tells me int codice; means "code" in English so you can possible use that field to encode your flag. For example using -1 for delete? This means your question is wrong as you indicated that you wanted use the word "delete". If you go this route then you have to audit your code and handle this flag. You probably don't want to print deleted records, and if you want to delete subsequent records then you want to skip ones already deleted. Do you want to retain or overwrite deleted records when you subsequently add a new record?

    I suggest you delete the record by truncating the file instead and with these other issues:

    1. Eliminate the struct name as you don't use it.
    2. Always use a maximum field with when reading strings with scanf() to avoid buffer overflow.
    3. Use symbolic constants instead of magic values.
    4. Always check the return value from fopen(). You missed it one place so combined it one place and pass file handle to the functions that need it.
    5. Update menu with the missing promntps. Also, to make the output easier to read only show the menu once at start-up.
    6. There is no point to passing the uninitialized variable c1 to inserisciCliente() and returning it by copy. Just use a local variable.
    7. (Not fixed) Move the calculation from the the switch to it's own function.
    8. (Not fixed) Check the return value of scanf() otherwise you may be operating on uninitialized variables. See for example how I handled the opz variable which I also moved to reduce it's scope.
    #define _POSIX_C_SOURCE 200112L
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    
    #define LEN 9
    #define DAT "file.dat"
    #define str(s) str2(s)
    #define str2(s) #s
    
    typedef struct {
        int codice;
        char nome[LEN+1];
        char merce[LEN+1];
        float prezzo;
    } cliente;
    
    cliente inserisciCliente() {
        cliente c1;
        printf("Inserisci il codice: ");
        scanf("%d", &c1.codice);
        getchar();
        printf("Inserisci il nome: ");
        scanf("%" str(LEN) "s", c1.nome);
        getchar();
        printf("Inserisci la merce: ");
        scanf("%" str(LEN) "s", c1.merce);
        printf("Inserisci prezzo: ");
        scanf("%f", &c1.prezzo);
        return c1;
    }
    
    void inserisciOrdine(FILE *lettura) {
        fseek(lettura, 0, SEEK_END);
        cliente c1 = inserisciCliente();
        fwrite(&c1, sizeof(c1), 1, lettura);
    }
    
    void stampaFile(FILE *lettura) {
        fseek(lettura, 0, SEEK_SET);
        cliente c1;
        while (fread(&c1, sizeof(cliente), 1, lettura) == 1) {
            printf("Codice: %d, Nome: %s, Merce: %s, Prezzo: %.2f\n", c1.codice, c1.nome, c1.merce, c1.prezzo);
        }
    }
    
    float calcolaPrezzo(FILE *lettura, char nome[10]) {
        fseek(lettura, 0, SEEK_SET);
        float prezzotot = 0;
        cliente c1;
        while (fread(&c1, sizeof(cliente), 1, lettura) == 1) {
            if (strcmp(nome, c1.nome) == 0) {
                prezzotot += c1.prezzo;
            }
        }
        return prezzotot;
    }
    
    void delete(FILE **lettura) {
        if(fseek(*lettura, 0, SEEK_END)) {
            perror("");
            return;
        }
        long end = ftell(*lettura);
        if(!end) return; // empty
        if(ftruncate(fileno(*lettura), end - sizeof(cliente))) {
            perror("");
            return;
        }
        *lettura = freopen(NULL, "a+r", *lettura);
        if(!*lettura) {
            perror("");
            return;
        }
    }
    
    int main() {
        FILE *lettura = fopen(DAT, "a+b");
        if(!lettura) {
            perror("Errore nell'apertura del file per la lettura.\n");
            return 1;
        }
        printf(
            "Inserisci un'opzione:\n"
            "1)Inserisci un cliente\n"
            "2)Stampa i clienti all'interno del file\n"
            "3)Ricerca un cliente e calcola l'importo dovuto\n"
            "4)Delete\n"
            "5)Exit\n"
        );
        for(;;) {
            printf("> ");
            enum { INSERT=1, SHOW, CALC, DELETE, EXIT=5 } opz;
            int rv = scanf("%d", &opz);
            if(rv == EOF)
                break;
            if(rv != 1)
                continue;
            switch (opz) {
                case INSERT:
                    inserisciOrdine(lettura);
                    break;
                case SHOW:
                    stampaFile(lettura);
                    break;
                case CALC: {
                           char nome[LEN+1];
                           float prezzotot = 0;
                           getchar();
                           scanf("%" str(LEN) "s", nome);
                           prezzotot = calcolaPrezzo(lettura, nome);
                           if (prezzotot)
                               printf("\nCliente non trovato");
                           else
                               printf("\nImporto dovuto dal cliente %s: %2.f euro", nome, prezzotot);
                           break;
                       }
                case DELETE:
                       delete(&lettura);
                       break;
                case EXIT:
                       goto done;
                default:
                       printf("\nOpzione non valida");
                       break;
            }
        }
    done:
        fclose(lettura);
        return 0;
    }
    
    Inserisci un'opzione:
    1)Inserisci un cliente
    2)Stampa i clienti all'interno del file
    3)Ricerca un cliente e calcola l'importo dovuto
    4)Delete
    5)Exit
    > 1
    Inserisci il codice: 1
    Inserisci il nome: 2
    Inserisci la merce: 3
    Inserisci prezzo: 4
    > 1
    Inserisci il codice: 2
    Inserisci il nome: 3
    Inserisci la merce: 4
    Inserisci prezzo: 5
    > 2
    Codice: 1, Nome: 2, Merce: 3, Prezzo: 4.00
    Codice: 2, Nome: 3, Merce: 4, Prezzo: 5.00
    > 4
    > 2
    Codice: 1, Nome: 2, Merce: 3, Prezzo: 4.00
    > 5