为文件创建自定义标头(元数据)

在这里,我想创建一个包含其他文件详细信息的标头,如其他文件的元数据。

如果我使用struct file_header静态值,这段代码可以正常工作。 如果我使用malloc for struct file_header那么我在这段代码中遇到了问题。 具体来说,我遇到了一个问题。 也许fwrite工作得很好。 代码在这里:

 #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; } 

输出:

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

有人可以帮我解决这个问题吗?

您正在使用64位计算机,因为您的指针长度为8个字节。

您正在尝试将数据写入文件,然后将其重新读入。您遇到了问题,因为指针写得不好。 (更准确地说:指针可以毫无问题地编写,但指针只在当前运行的程序中有意义,并且很少适合写入磁盘,甚至更少适合从磁盘读回。)

这部分代码说明了问题:

 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)); } 

附注:

  • 将文件名作为main()的参数,或者至少变为变量。 将名称写出两次使得很难改变。
  • 你正在做一些错误检测是好的。 但是,我不打算批评它,尽管它有很大的改进空间。

主要评论:

  • 你看到size of Header is 8在输出中size of Header is 8 ,因为header是一个指针。 sizeof(metadata)header指向的类型)要大得多,可能是48个字节,但这取决于编译器如何在结构中对齐和打包数据。

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

此代码将8个字节的数据写入文件。 它写的是存储header变量的地址。 它不会写出它指向的任何数据。

什么会更接近你所追求的(但仍然不会起作用)是:

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

这将写入48个字节或其左右的文件。 但是,您的文件不包含文件名; 它只包含指向文件编写时文件名存储位置的指针。 这里要非常小心。 如果您阅读此文件,您甚至可能会看到它似乎正常工作,因为名称可能尚未从内存中删除。

要将文件名放入文件中,您必须单独处理每个文件名。 你必须决定一个约定。 例如,您可能会确定该名称将以2字节unsigned short作为前缀,其中包含文件名L的长度,后跟L + 1个字节的数据,其中包含文件名及其终端NUL '\0' 。 然后,您将编写每个文件数据的其他(固定大小)部分。 并且您将为每个文件重复此过程。 读取文件的逆操作需要理解书面结构。 在您期望文件名的位置,您将读取两个字节的长度,并且可以使用该长度为文件名分配空间。 然后将L + 1个字节读入新分配的文件名中。 然后,您读取该文件的其他固定长度数据,然后转到下一个文件。

如果你想在一个fwrite()然后fread() ,你将不得不修改你的数据结构:

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

您可以决定允许的最大文件名长度是多少,但它是固定的。 如果你的名字很短,你就不会占用所有的空间; 如果你的名字很长,它们可能会被截断。 您的metadata结构大小现在显着增加(至少如果MAX_PERMITTED_FILENAME_LENGTH是合理的大小,比如介于32和1024字节之间)。 但是您可以使用它在一个操作中读取和写入整个metadata结构。


感谢您的回复,但我是C新手,那么我怎么能实现这个目标呢?

最终,你将能够像这样编码它。

 #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); } 

我将其编译为dump_file ,并按如下所示运行它:

 $ 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: $ 

我应该将err_exit()重命名为err_sysexit()并修改error handling,以便在该函数内处理errno和相应的字符串,而不是重复地将errnostrerror(errno)到对err_exit()的调用中。


来自评论的信息

将一些相当广泛的评论转移到问题中:

我尝试了上面的代码,但是在File : 4之后得到了分段错误,这意味着数据写入工作正常但我在数据读取方面遇到了一些问题。 Nimit

我尝试了上面的代码,当我从文件中读取数据时,我遇到了分段错误。 user1089679

糟糕: valgrindrelease_header()给出了关于写入无效的警告。 这会搞砸了。 不过难以解决 - 它是release_header()中的第二个memset()导致恶作剧; 我不小心省略了&符号:

 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 

这已在代码中修复。 请注意,两个memset()操作都在代码中,以确保如果重用内存,它不包含以前的有效数据,这是一个风险,因为代码最初将指针写入磁盘然后再次读回它们。 memset()调用不会出现在普通的生产代码中。

请注意, odx是一个家庭式hex转储程序(Mac OS X默认没有hd程序)。 您的系统可能已经有hd for hex dump,或者您可以尝试高清或尝试使用自己的Google Fu来寻找替代方案。

只是想问一下,我想在跨平台上运行这个程序,那么低位机器有什么问题吗? Nimit

这个代码在big-endian或little-endian机器上没有问题; 如果将数据从小端(Intel)机器传输到big-endian(SPARC,PPC,...)机器或反之亦然,则会出现问题。 代码可能对32位和64位版本也很敏感; 我没有将字段大小定义为n位,而是将类似int的方便类型定义为可在系统之间更改。 如果你想要可移植数据,决定字段大小(主要是1,2,4,8字节,至少是非字符串数据),然后以标准方式写入 - MSB优先(big-endian)或也许LSB第一(小端)。