Crea un’intestazione personalizzata (metadati) per i file

Qui voglio creare un’intestazione che contiene altri dettagli di file come i metadati di altri file.

Questo codice funziona perfettamente se uso valori statici per struct file_header . Se sto usando malloc per struct file_header allora sto struct file_header un problema in questo codice. Nello specifico, sto avendo un problema in fread . Forse la fwrite funzionato perfettamente. Il codice è qui:

 #include  #include  #include  #include  #include  #include  #include  char path[1024] = "/home/test/main/Integration/testing/package_DIR"; //int count = 5; struct files { char *file_name; int file_size; }; typedef struct file_header { int file_count; struct files file[5]; } metadata; metadata *create_header(); int main() { FILE *file = fopen("/home/test/main/Integration/testing/file.txt", "w"); metadata *header; header = create_header(); if(header != NULL) { printf("size of Header is %d\n",sizeof(header)); } if (file != NULL) { if (fwrite(&header, sizeof(header), 1, file) < 1) { puts("short count on fwrite"); } fclose(file); } file = fopen("/home/test/main/Integration/testing/file.txt", "rb"); if (file != NULL) { metadata header = { 0 }; if (fread(&header, sizeof(header), 1, file) d_type == DT_REG) { /* If the entry is a regular file */ header->file[file_count].file_name = (char *)malloc(sizeof(char)*strlen(entry->d_name)); strcpy(header->file[file_count].file_name,entry->d_name); //Put static but i have logic for this i will apply later. header->file[file_count].file_size = 10; file_count++; } } header->file_count = file_count; closedir(dirp); //printf("File Count : %d\n", file_count); return header; } 

Produzione:

 size of Header is 8 short count on fread File Name = (null) File count = 21918336 File Size = 0 

Qualcuno può aiutarmi a risolvere questo problema?

Stai lavorando su una macchina a 64 bit perché i tuoi puntatori sono lunghi 8 byte.

Stai provando a scrivere i dati su un file e poi a leggerli di nuovo. Stai riscontrando problemi perché i puntatori non scrivono molto bene. (Più precisamente: i puntatori possono essere scritti senza problemi, ma i puntatori hanno solo significato nel programma corrente in esecuzione e sono raramente adatti per la scrittura su disco e ancor più raramente adatti per la lettura dal disco.)

Questa parte del tuo codice illustra il problema:

 struct files { char *file_name; int file_size; }; typedef struct file_header { int file_count; struct files file[5]; } metadata; metadata *create_header(); int main() { FILE *file = fopen("/home/test/main/Integration/testing/file.txt", "w"); metadata *header; header = create_header(); if(header != NULL) { printf("size of Header is %d\n",sizeof(header)); } 

Commenti laterali:

  • Trasforma il nome del file in argomento su main() , o, almeno, in una variabile. Scrivere il nome due volte rende difficile cambiare.
  • È bene che tu stia facendo un rilevamento degli errori. Tuttavia, non ho intenzione di criticarlo, sebbene ci sia un considerevole margine di miglioramento.

Commenti principali:

  • Vedete che la size of Header is 8 nell’output perché l’ header è un puntatore. La sizeof(metadata) (il tipo a cui punta l’ header ) è molto più grande, probabilmente 48 byte, ma dipende da come il compilatore allinea e impacchetta i dati nella struttura.

     if (file != NULL) { if (fwrite(&header, sizeof(header), 1, file) < 1) { puts("short count on fwrite"); } fclose(file); } 

Questo codice scrive 8 byte di dati nel file. Quello che scrive è l'indirizzo in cui è memorizzata la variabile di header . Non scrive nulla dei dati a cui punta.

Ciò che potrebbe avvicinarsi a ciò che stai cercando (ma non funziona ancora) è:

  if (fwrite(header, sizeof(*header), 1, file) < 1) { puts("short count on fwrite"); } 

Questo scriverà 48 byte o circa nel file. Tuttavia, il tuo file non conterrà i nomi dei file; conterrà semplicemente i puntatori a dove sono stati memorizzati i nomi dei file nel momento in cui è stato scritto il file. Stai molto attento qui. Se leggi questo file, potresti persino vedere che sembra funzionare perché i nomi potrebbero non essere stati ancora cancellati dalla memoria.

Per ottenere i nomi dei file nel file, dovrai gestirli separatamente. Dovrai decidere su una convention. Ad esempio, potresti decidere che il nome sarà preceduto da un unsigned short 2 byte che contiene la lunghezza del nome del file, L, seguito da L + 1 byte di dati contenenti il ​​nome del file e il suo terminale NUL '\0' . Quindi scriveresti le altre parti (a dimensione fissa) dei dati per file. E ripeterai questo processo per ognuno dei file. L'operazione inversa, leggendo il file, richiede la comprensione della struttura scritta. Nel punto in cui ti aspetti un nome di file, leggerai la lunghezza di due byte, e puoi usare quella lunghezza per allocare lo spazio per il nome del file. Quindi leggi i byte L + 1 nel nome del file appena assegnato. Quindi leggi gli altri dati a lunghezza fissa per il file, quindi passa al file successivo.

Se vuoi essere in grado di fare tutto in un solo fwrite() e poi fread() , dovrai rivedere la struttura dei tuoi dati:

 struct files { char file_name[MAX_PERMITTED_FILENAME_LENGTH]; int file_size; }; 

Si arriva a decidere quale sia la lunghezza massima consentita per il nome del file, ma è fissa. Se i tuoi nomi sono brevi, non usi tutto lo spazio; se i tuoi nomi sono lunghi, potrebbero essere troncati. Le dimensioni della struttura dei metadata ora aumentano notevolmente (almeno se MAX_PERMITTED_FILENAME_LENGTH è una dimensione ragionevole, ad esempio tra 32 e 1024 byte). Ma con questa puoi leggere e scrivere l'intera struttura dei metadata in un'unica operazione.


Grazie per la tua risposta, ma io sono nuovo in C, quindi come posso ottenere questa cosa?

Alla fine, sarai in grado di codificarlo in qualche modo come questo.

 #include  #include  #include  #include  #include  #include  enum { MAX_FILES = 5 }; struct files { char *file_name; int file_size; }; typedef struct file_header { int file_count; struct files file[MAX_FILES]; } metadata; static void err_exit(const char *format, ...); static metadata *create_header(const char *directory); static void release_header(metadata *header); static void write_header(FILE *fp, const metadata *header); static metadata *read_header(FILE *fp); static void dump_header(FILE *fp, const char *tag, const metadata *header); int main(int argc, char **argv) { if (argc != 3) err_exit("Usage: %s file directory\n", argv[0]); const char *name = argv[1]; const char *path = argv[2]; FILE *fp = fopen(name, "wb"); if (fp == 0) err_exit("Failed to open file %s for writing (%d: %s)\n", name, errno, strerror(errno)); metadata *header = create_header(path); dump_header(stdout, "Data to be written", header); write_header(fp, header); fclose(fp); // Ignore error on close release_header(header); if ((fp = fopen(name, "rb")) == 0) err_exit("Failed to open file %s for reading (%d: %s)\n", name, errno, strerror(errno)); metadata *read_info = read_header(fp); dump_header(stdout, "Data as read", read_info); release_header(read_info); fclose(fp); // Ignore error on close return 0; } static metadata *create_header(const char *path) { int file_count = 0; DIR * dirp = opendir(path); struct dirent * entry; if (dirp == 0) err_exit("Failed to open directory %s (%d: %s)\n", path, errno, strerror(errno)); metadata *header = (metadata *)malloc(sizeof(metadata)); if (header == 0) err_exit("Failed to malloc space for header (%d: %s)\n", errno, strerror(errno)); header->file_count = 0; while ((entry = readdir(dirp)) != NULL && file_count < MAX_FILES) { // d_type is not portable - POSIX says you can only rely on d_name and d_ino if (entry->d_type == DT_REG) { /* If the entry is a regular file */ // Avoid off-by-one under-allocation by using strdup() header->file[file_count].file_name = strdup(entry->d_name); if (header->file[file_count].file_name == 0) err_exit("Failed to strdup() file %s (%d: %s)\n", entry->d_name, errno, strerror(errno)); //Put static but i have logic for this i will apply later. header->file[file_count].file_size = 10; file_count++; } } header->file_count = file_count; closedir(dirp); //printf("File Count : %d\n", file_count); return header; } static void write_header(FILE *fp, const metadata *header) { if (fwrite(&header->file_count, sizeof(header->file_count), 1, fp) != 1) err_exit("Write error on file count (%d: %s)\n", errno, strerror(errno)); const struct files *files = header->file; for (int i = 0; i < header->file_count; i++) { unsigned short name_len = strlen(files[i].file_name) + 1; if (fwrite(&name_len, sizeof(name_len), 1, fp) != 1) err_exit("Write error on file name length (%d: %s)\n", errno, strerror(errno)); if (fwrite(files[i].file_name, name_len, 1, fp) != 1) err_exit("Write error on file name (%d: %s)\n", errno, strerror(errno)); if (fwrite(&files[i].file_size, sizeof(files[i].file_size), 1, fp) != 1) err_exit("Write error on file size (%d: %s)\n", errno, strerror(errno)); } } static metadata *read_header(FILE *fp) { metadata *header = malloc(sizeof(*header)); if (header == 0) err_exit("Failed to malloc space for header (%d:%s)\n", errno, strerror(errno)); if (fread(&header->file_count, sizeof(header->file_count), 1, fp) != 1) err_exit("Read error on file count (%d: %s)\n", errno, strerror(errno)); struct files *files = header->file; for (int i = 0; i < header->file_count; i++) { unsigned short name_len; if (fread(&name_len, sizeof(name_len), 1, fp) != 1) err_exit("Read error on file name length (%d: %s)\n", errno, strerror(errno)); files[i].file_name = malloc(name_len); if (files[i].file_name == 0) err_exit("Failed to malloc space for file name (%d:%s)\n", errno, strerror(errno)); if (fread(files[i].file_name, name_len, 1, fp) != 1) err_exit("Read error on file name (%d: %s)\n", errno, strerror(errno)); if (fread(&files[i].file_size, sizeof(files[i].file_size), 1, fp) != 1) err_exit("Read error on file size (%d: %s)\n", errno, strerror(errno)); } return(header); } static void dump_header(FILE *fp, const char *tag, const metadata *header) { const struct files *files = header->file; fprintf(fp, "Metadata: %s\n", tag); fprintf(fp, "File count: %d\n", header->file_count); for (int i = 0; i < header->file_count; i++) fprintf(fp, "File %d: size %5d, name %s\n", i, files[i].file_size, files[i].file_name); } static void release_header(metadata *header) { for (int i = 0; i < header->file_count; i++) { /* Zap file name, and pointer to file name */ memset(header->file[i].file_name, 0xDD, strlen(header->file[i].file_name)+1); free(header->file[i].file_name); memset(&header->file[i].file_name, 0xEE, sizeof(header->file[i].file_name)); } free(header); } static void err_exit(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(EXIT_FAILURE); } 

L'ho compilato come dump_file e l'ho eseguito come mostrato:

 $ dump_file xyz . Metadata: Data to be written File count: 5 File 0: size 10, name .gitignore File 1: size 10, name args.c File 2: size 10, name atob.c File 3: size 10, name bp.pl File 4: size 10, name btwoc.c Metadata: Data as read File count: 5 File 0: size 10, name .gitignore File 1: size 10, name args.c File 2: size 10, name atob.c File 3: size 10, name bp.pl File 4: size 10, name btwoc.c $ odx xyz 0x0000: 05 00 00 00 0B 00 2E 67 69 74 69 67 6E 6F 72 65 .......gitignore 0x0010: 00 0A 00 00 00 07 00 61 72 67 73 2E 63 00 0A 00 .......args.c... 0x0020: 00 00 07 00 61 74 6F 62 2E 63 00 0A 00 00 00 06 ....atob.c...... 0x0030: 00 62 70 2E 70 6C 00 0A 00 00 00 08 00 62 74 77 .bp.pl.......btw 0x0040: 6F 63 2E 63 00 0A 00 00 00 oc.c..... 0x0049: $ 

Probabilmente avrei dovuto rinominare err_exit() come err_sysexit() e rivisto la gestione degli errori in modo che errno e la stringa corrispondente fossero gestiti all'interno di quella funzione, piuttosto che aggiungere ripetutamente errno e strerror(errno) alle chiamate a err_exit() .


Informazioni dai commenti

Trasferendo alcuni dei commenti piuttosto ampi nella domanda:

Ho provato sopra il codice, ma ho riscontrato un errore di segmentazione dopo File : 4 , il che significa che la scrittura dei dati funziona correttamente ma sto avendo qualche problema con la lettura dei dati. Nimit

Ho provato sopra il codice e sto ricevendo un errore di segmentazione mentre sto leggendo i dati dal file. user1089679

Oops: valgrind mi sta dando degli avvertimenti su una scrittura non valida in release_header() . Questo avrebbe rovinato tutto. Non è difficile da risolvere, però - è il secondo memset() in release_header() che sta causando il danno; Ho omesso accidentalmente la e commerciale:

 memset( header->file[i].file_name, 0xEE, sizeof(header->file[i].file_name)); // Broken memset(&header->file[i].file_name, 0xEE, sizeof(header->file[i].file_name)); // Correct 

Questo è stato risolto nel codice. Notare che entrambe le operazioni memset() trovano nel codice per assicurare che, se la memoria viene riutilizzata, non contenga dati validi precedenti, il che rappresentava un rischio dato che il codice stava originariamente scrivendo i puntatori sul disco e quindi li leggeva di nuovo. Le chiamate memset() non sarebbero presenti nel normale codice di produzione.

Nota che odx è un programma di dump esadecimale home-brew (Mac OS X non ha un programma hd di default). Il tuo sistema potrebbe già avere l' hd per il dump esadecimale, oppure potresti provare l' hd o provare il tuo Google Fu per trovare alternative.

Voglio solo chiederlo, voglio eseguire questo programma su più piattaforms, quindi c'è qualche problema con le macchine a basso bit? Nimit

Non c'è alcun problema con questo codice su macchine big-endian o little-endian; ci sarebbero problemi se si prendessero dati da una macchina little-endian (Intel) a una macchina big-endian (SPARC, PPC, ...) o viceversa. Il codice è probabilmente anche sensibile ai build a 32-bit vs 64-bit; Non ho definito le dimensioni del campo come n-bit ma come tipi convenienti come int che possono cambiare tra i sistemi. Se si desiderano dati portatili, decidere le dimensioni del campo (1, 2, 4, 8 byte, soprattutto, almeno per i dati non stringa), quindi scriverlo in un modo standard - MSB prima (big-endian) o forse LSB prima (little-endian).