From ab2e9e81e187d427b22ac969f47900d3de041264 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Tue, 23 Jun 2009 13:03:00 -0700 Subject: Add file mode to the file-backup saved state blobs This change puts the file's access mode into the saved-state blob used by the file backup helpers. The tests have been updated for the new blob content format. What this change *doesn't* do is actually backup/restore the file mode. This change is a prerequisite for that, but mode preservation in backup/restore will require adding metadata to the backup data stream itself, so will be approached a bit more carefully. (Also fixed one outright bug in the test program: ReadEntityData() had been changed to return a ssize_t union of either a byte-count or a negative number indicating error, but the test program was still assuming that nonzero == error, and was spuriously failing.) --- libs/utils/BackupHelpers.cpp | 73 +++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 31 deletions(-) (limited to 'libs/utils/BackupHelpers.cpp') diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp index d65a457f39..67d07fed85 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -302,6 +302,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD r.s.modTime_sec = st.st_mtime; r.s.modTime_nsec = 0; // workaround sim breakage //r.s.modTime_nsec = st.st_mtime_nsec; + r.s.mode = st.st_mode; r.s.size = st.st_size; // we compute the crc32 later down below, when we already have the file open. @@ -349,12 +350,12 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD g.s.crc32 = compute_crc32(fd); LOGP("%s", q.string()); - LOGP(" new: modTime=%d,%d size=%-3d crc32=0x%08x", - f.modTime_sec, f.modTime_nsec, f.size, f.crc32); - LOGP(" old: modTime=%d,%d size=%-3d crc32=0x%08x", - g.s.modTime_sec, g.s.modTime_nsec, g.s.size, g.s.crc32); + LOGP(" new: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x", + f.modTime_sec, f.modTime_nsec, f.mode, f.size, f.crc32); + LOGP(" old: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x", + g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32); if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec - || f.size != g.s.size || f.crc32 != g.s.crc32) { + || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) { write_update_file(dataStream, fd, p, g.file.string()); } @@ -450,6 +451,7 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) r.s.modTime_sec = st.st_mtime; r.s.modTime_nsec = 0; // workaround sim breakage //r.s.modTime_nsec = st.st_mtime_nsec; + r.s.mode = st.st_mode; r.s.size = st.st_size; r.s.crc32 = crc; @@ -623,6 +625,7 @@ backup_helper_test_four() states[0].modTime_sec = 0xfedcba98; states[0].modTime_nsec = 0xdeadbeef; + states[0].mode = 0777; // decimal 511, hex 0x000001ff states[0].size = 0xababbcbc; states[0].crc32 = 0x12345678; states[0].nameLen = -12; @@ -632,6 +635,7 @@ backup_helper_test_four() states[1].modTime_sec = 0x93400031; states[1].modTime_nsec = 0xdeadbeef; + states[1].mode = 0666; // decimal 438, hex 0x000001b6 states[1].size = 0x88557766; states[1].crc32 = 0x22334422; states[1].nameLen = -1; @@ -641,6 +645,7 @@ backup_helper_test_four() states[2].modTime_sec = 0x33221144; states[2].modTime_nsec = 0xdeadbeef; + states[2].mode = 0744; // decimal 484, hex 0x000001e4 states[2].size = 0x11223344; states[2].crc32 = 0x01122334; states[2].nameLen = 0; @@ -650,6 +655,7 @@ backup_helper_test_four() states[3].modTime_sec = 0x33221144; states[3].modTime_nsec = 0xdeadbeef; + states[3].mode = 0755; // decimal 493, hex 0x000001ed states[3].size = 0x11223344; states[3].crc32 = 0x01122334; states[3].nameLen = 0; @@ -669,35 +675,38 @@ backup_helper_test_four() static const unsigned char correct_data[] = { // header 0x53, 0x6e, 0x61, 0x70, 0x04, 0x00, 0x00, 0x00, - 0x46, 0x69, 0x6c, 0x65, 0xac, 0x00, 0x00, 0x00, + 0x46, 0x69, 0x6c, 0x65, 0xbc, 0x00, 0x00, 0x00, // bytes_of_padding 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xbe, 0xad, 0xde, - 0xbc, 0xbc, 0xab, 0xab, 0x78, 0x56, 0x34, 0x12, - 0x10, 0x00, 0x00, 0x00, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x61, 0x64, - 0x64, 0x69, 0x6e, 0x67, + 0xff, 0x01, 0x00, 0x00, 0xbc, 0xbc, 0xab, 0xab, + 0x78, 0x56, 0x34, 0x12, 0x10, 0x00, 0x00, 0x00, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66, + 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, // bytes_of_padding3 0x31, 0x00, 0x40, 0x93, 0xef, 0xbe, 0xad, 0xde, - 0x66, 0x77, 0x55, 0x88, 0x22, 0x44, 0x33, 0x22, - 0x11, 0x00, 0x00, 0x00, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x61, 0x64, - 0x64, 0x69, 0x6e, 0x67, 0x33, 0xab, 0xab, 0xab, + 0xb6, 0x01, 0x00, 0x00, 0x66, 0x77, 0x55, 0x88, + 0x22, 0x44, 0x33, 0x22, 0x11, 0x00, 0x00, 0x00, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66, + 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, + 0x33, 0xab, 0xab, 0xab, // bytes of padding2 0x44, 0x11, 0x22, 0x33, 0xef, 0xbe, 0xad, 0xde, - 0x44, 0x33, 0x22, 0x11, 0x34, 0x23, 0x12, 0x01, - 0x12, 0x00, 0x00, 0x00, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x61, 0x64, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x32, 0xab, 0xab, + 0xe4, 0x01, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11, + 0x34, 0x23, 0x12, 0x01, 0x12, 0x00, 0x00, 0x00, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66, + 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x32, 0xab, 0xab, // bytes of padding3 0x44, 0x11, 0x22, 0x33, 0xef, 0xbe, 0xad, 0xde, - 0x44, 0x33, 0x22, 0x11, 0x34, 0x23, 0x12, 0x01, - 0x13, 0x00, 0x00, 0x00, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x61, 0x64, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x5f, 0x31, 0xab + 0xed, 0x01, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11, + 0x34, 0x23, 0x12, 0x01, 0x13, 0x00, 0x00, 0x00, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66, + 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x5f, 0x31, 0xab }; err = compare_file(filename, correct_data, sizeof(correct_data)); @@ -731,14 +740,14 @@ backup_helper_test_four() const FileState state = readSnapshot.valueAt(i); if (name != filenames[i] || states[i].modTime_sec != state.modTime_sec - || states[i].modTime_nsec != state.modTime_nsec + || states[i].modTime_nsec != state.modTime_nsec || states[i].mode != state.mode || states[i].size != state.size || states[i].crc32 != states[i].crc32) { - fprintf(stderr, "state %d expected={%d/%d, 0x%08x, 0x%08x, %3d} '%s'\n" - " actual={%d/%d, 0x%08x, 0x%08x, %3d} '%s'\n", i, - states[i].modTime_sec, states[i].modTime_nsec, states[i].size, states[i].crc32, - name.length(), filenames[i].string(), - state.modTime_sec, state.modTime_nsec, state.size, state.crc32, state.nameLen, - name.string()); + fprintf(stderr, "state %d expected={%d/%d, 0x%08x, %04o, 0x%08x, %3d} '%s'\n" + " actual={%d/%d, 0x%08x, %04o, 0x%08x, %3d} '%s'\n", i, + states[i].modTime_sec, states[i].modTime_nsec, states[i].mode, states[i].size, + states[i].crc32, name.length(), filenames[i].string(), + state.modTime_sec, state.modTime_nsec, state.mode, state.size, state.crc32, + state.nameLen, name.string()); matched = false; } } @@ -839,6 +848,7 @@ test_read_header_and_entity(BackupDataReader& reader, const char* str) size_t actualSize; bool done; int type; + ssize_t nRead; // printf("\n\n---------- test_read_header_and_entity -- %s\n\n", str); @@ -873,8 +883,9 @@ test_read_header_and_entity(BackupDataReader& reader, const char* str) goto finished; } - err = reader.ReadEntityData(buf, bufSize); - if (err != NO_ERROR) { + nRead = reader.ReadEntityData(buf, bufSize); + if (nRead < 0) { + err = reader.Status(); fprintf(stderr, "ReadEntityData failed with %s\n", strerror(err)); goto finished; } -- cgit v1.2.3-59-g8ed1b From 5c2882b25c5f50ad5fe0dd658e4076b0c857dddc Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Tue, 23 Jun 2009 17:35:11 -0700 Subject: Preserve file access mode when backing up / restoring files This change adds a fixed-size metadata block at the head of each file's content entity. The block is versioned, and fixed-size on the theory that it might be nice to be able to recover the content (if not the full metadata) of the files if we're ever confronted with data backed up some hypothetical future helper that stored expanded metadata. The net effect is that now on restore, we assign the same access mode to the file that it originally had when backed up. Also, some of the code was failing to properly free transient heap-based buffers when it encountered errors. This has been fixed with the addition of a tiny stack-based object whose job it is to free() its designated pointer from its destructor. --- libs/utils/BackupHelpers.cpp | 102 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 13 deletions(-) (limited to 'libs/utils/BackupHelpers.cpp') diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp index 67d07fed85..3346614761 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -41,7 +41,43 @@ namespace android { #define MAGIC0 0x70616e53 // Snap #define MAGIC1 0x656c6946 // File -#if 1 // TEST_BACKUP_HELPERS +/* + * File entity data format (v1): + * + * - 4-byte version number of the metadata, little endian (0x00000001 for v1) + * - 12 bytes of metadata + * - the file data itself + * + * i.e. a 16-byte metadata header followed by the raw file data. If the + * restore code does not recognize the metadata version, it can still + * interpret the file data itself correctly. + * + * file_metadata_v1: + * + * - 4 byte version number === 0x00000001 (little endian) + * - 4-byte access mode (little-endian) + * - undefined (8 bytes) + */ + +struct file_metadata_v1 { + int version; + int mode; + int undefined_1; + int undefined_2; +}; + +const static int CURRENT_METADATA_VERSION = 1; + +// auto-free buffer management object +class StAutoFree { +public: + StAutoFree(void* buffer) { mBuf = buffer; } + ~StAutoFree() { free(mBuf); } +private: + void* mBuf; +}; + +#if 0 // TEST_BACKUP_HELPERS #define LOGP(f, x...) printf(f "\n", x) #else #define LOGP(x...) LOGD(x) @@ -181,29 +217,48 @@ write_delete_file(BackupDataWriter* dataStream, const String8& key) } static int -write_update_file(BackupDataWriter* dataStream, int fd, const String8& key, +write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& key, char const* realFilename) { - LOGP("write_update_file %s (%s)\n", realFilename, key.string()); + LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.string(), mode); const int bufsize = 4*1024; int err; int amt; int fileSize; int bytesLeft; + file_metadata_v1 metadata; char* buf = (char*)malloc(bufsize); + StAutoFree _autoFree(buf); + int crc = crc32(0L, Z_NULL, 0); - bytesLeft = fileSize = lseek(fd, 0, SEEK_END); + fileSize = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); + if (sizeof(metadata) != 16) { + LOGE("ERROR: metadata block is the wrong size!"); + } + + bytesLeft = fileSize + sizeof(metadata); err = dataStream->WriteEntityHeader(key, bytesLeft); if (err != 0) { return err; } + // store the file metadata first + metadata.version = tolel(CURRENT_METADATA_VERSION); + metadata.mode = tolel(mode); + metadata.undefined_1 = metadata.undefined_2 = 0; + err = dataStream->WriteEntityData(&metadata, sizeof(metadata)); + if (err != 0) { + return err; + } + bytesLeft -= sizeof(metadata); // bytesLeft should == fileSize now + + // now store the file content while ((amt = read(fd, buf, bufsize)) != 0 && bytesLeft > 0) { bytesLeft -= amt; if (bytesLeft < 0) { @@ -232,8 +287,6 @@ write_update_file(BackupDataWriter* dataStream, int fd, const String8& key, " You aren't doing proper locking!", realFilename, fileSize, fileSize-bytesLeft); } - free(buf); - return NO_ERROR; } @@ -241,11 +294,19 @@ static int write_update_file(BackupDataWriter* dataStream, const String8& key, char const* realFilename) { int err; + struct stat st; + + err = stat(realFilename, &st); + if (err < 0) { + return errno; + } + int fd = open(realFilename, O_RDONLY); if (fd == -1) { return errno; } - err = write_update_file(dataStream, fd, key, realFilename); + + err = write_update_file(dataStream, fd, st.st_mode, key, realFilename); close(fd); return err; } @@ -257,6 +318,8 @@ compute_crc32(int fd) int amt; char* buf = (char*)malloc(bufsize); + StAutoFree _autoFree(buf); + int crc = crc32(0L, Z_NULL, 0); lseek(fd, 0, SEEK_SET); @@ -265,8 +328,6 @@ compute_crc32(int fd) crc = crc32(crc, (Bytef*)buf, amt); } - free(buf); - return crc; } @@ -356,7 +417,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32); if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) { - write_update_file(dataStream, fd, p, g.file.string()); + write_update_file(dataStream, fd, g.s.mode, p, g.file.string()); } close(fd); @@ -416,8 +477,22 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) return err; } - // TODO: World readable/writable for now. - mode = 0666; + // Get the metadata block off the head of the file entity and use that to + // set up the output file + file_metadata_v1 metadata; + amt = in->ReadEntityData(&metadata, sizeof(metadata)); + if (amt != sizeof(metadata)) { + LOGW("Could not read metadata for %s -- %ld / %s", filename.string(), + (long)amt, strerror(errno)); + return EIO; + } + metadata.version = fromlel(metadata.version); + metadata.mode = fromlel(metadata.mode); + if (metadata.version > CURRENT_METADATA_VERSION) { + LOGW("Restoring file with unsupported metadata version %d (currently %d)", + metadata.version, CURRENT_METADATA_VERSION); + } + mode = metadata.mode; // Write the file and compute the crc crc = crc32(0L, Z_NULL, 0); @@ -512,6 +587,7 @@ compare_file(const char* path, const unsigned char* data, int len) fprintf(stderr, "malloc(%d) failed\n", len); return ENOMEM; } + StAutoFree _autoFree(contents); bool sizesMatch = true; amt = lseek(fd, 0, SEEK_END); @@ -843,6 +919,7 @@ test_read_header_and_entity(BackupDataReader& reader, const char* str) int err; int bufSize = strlen(str)+1; char* buf = (char*)malloc(bufSize); + StAutoFree _autoFree(buf); String8 string; int cookie = 0x11111111; size_t actualSize; @@ -904,7 +981,6 @@ finished: if (err != NO_ERROR) { fprintf(stderr, "test_read_header_and_entity failed with %s\n", strerror(err)); } - free(buf); return err; } -- cgit v1.2.3-59-g8ed1b From 6441a76b2ed24eddcadc6a0763dd674c3aa38c46 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Wed, 24 Jun 2009 11:20:51 -0700 Subject: Put back LOGP -> printf in the backup helper code --- libs/utils/BackupHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libs/utils/BackupHelpers.cpp') diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp index 3346614761..b309dbf43e 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -77,7 +77,7 @@ private: void* mBuf; }; -#if 0 // TEST_BACKUP_HELPERS +#if 1 // TEST_BACKUP_HELPERS #define LOGP(f, x...) printf(f "\n", x) #else #define LOGP(x...) LOGD(x) -- cgit v1.2.3-59-g8ed1b From bd95c1d3af82e329ada195876348383b7859ce85 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Wed, 24 Jun 2009 13:57:29 -0700 Subject: Only report "unknown metadata" once per restore helper Also removes the auto-free object, replacing it with direct memory manipulation. --- include/utils/BackupHelpers.h | 1 + libs/utils/BackupHelpers.cpp | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) (limited to 'libs/utils/BackupHelpers.cpp') diff --git a/include/utils/BackupHelpers.h b/include/utils/BackupHelpers.h index 759a0cc6d5..b1f504512f 100644 --- a/include/utils/BackupHelpers.h +++ b/include/utils/BackupHelpers.h @@ -137,6 +137,7 @@ public: private: void* m_buf; + bool m_loggedUnknownMetadata; KeyedVector m_files; }; diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp index b309dbf43e..99a4abcb6e 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -68,15 +68,6 @@ struct file_metadata_v1 { const static int CURRENT_METADATA_VERSION = 1; -// auto-free buffer management object -class StAutoFree { -public: - StAutoFree(void* buffer) { mBuf = buffer; } - ~StAutoFree() { free(mBuf); } -private: - void* mBuf; -}; - #if 1 // TEST_BACKUP_HELPERS #define LOGP(f, x...) printf(f "\n", x) #else @@ -230,8 +221,6 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& file_metadata_v1 metadata; char* buf = (char*)malloc(bufsize); - StAutoFree _autoFree(buf); - int crc = crc32(0L, Z_NULL, 0); @@ -245,6 +234,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& bytesLeft = fileSize + sizeof(metadata); err = dataStream->WriteEntityHeader(key, bytesLeft); if (err != 0) { + free(buf); return err; } @@ -254,6 +244,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& metadata.undefined_1 = metadata.undefined_2 = 0; err = dataStream->WriteEntityData(&metadata, sizeof(metadata)); if (err != 0) { + free(buf); return err; } bytesLeft -= sizeof(metadata); // bytesLeft should == fileSize now @@ -266,6 +257,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& } err = dataStream->WriteEntityData(buf, amt); if (err != 0) { + free(buf); return err; } } @@ -279,6 +271,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& bytesLeft -= amt; err = dataStream->WriteEntityData(buf, amt); if (err != 0) { + free(buf); return err; } } @@ -287,6 +280,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& " You aren't doing proper locking!", realFilename, fileSize, fileSize-bytesLeft); } + free(buf); return NO_ERROR; } @@ -318,8 +312,6 @@ compute_crc32(int fd) int amt; char* buf = (char*)malloc(bufsize); - StAutoFree _autoFree(buf); - int crc = crc32(0L, Z_NULL, 0); lseek(fd, 0, SEEK_SET); @@ -328,6 +320,7 @@ compute_crc32(int fd) crc = crc32(crc, (Bytef*)buf, amt); } + free(buf); return crc; } @@ -451,6 +444,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD RestoreHelperBase::RestoreHelperBase() { m_buf = malloc(RESTORE_BUF_SIZE); + m_loggedUnknownMetadata = false; } RestoreHelperBase::~RestoreHelperBase() @@ -489,8 +483,11 @@ RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) metadata.version = fromlel(metadata.version); metadata.mode = fromlel(metadata.mode); if (metadata.version > CURRENT_METADATA_VERSION) { - LOGW("Restoring file with unsupported metadata version %d (currently %d)", - metadata.version, CURRENT_METADATA_VERSION); + if (!m_loggedUnknownMetadata) { + m_loggedUnknownMetadata = true; + LOGW("Restoring file with unsupported metadata version %d (currently %d)", + metadata.version, CURRENT_METADATA_VERSION); + } } mode = metadata.mode; @@ -587,7 +584,6 @@ compare_file(const char* path, const unsigned char* data, int len) fprintf(stderr, "malloc(%d) failed\n", len); return ENOMEM; } - StAutoFree _autoFree(contents); bool sizesMatch = true; amt = lseek(fd, 0, SEEK_END); @@ -614,6 +610,7 @@ compare_file(const char* path, const unsigned char* data, int len) } } + free(contents); return contentsMatch && sizesMatch ? 0 : 1; } @@ -919,7 +916,6 @@ test_read_header_and_entity(BackupDataReader& reader, const char* str) int err; int bufSize = strlen(str)+1; char* buf = (char*)malloc(bufSize); - StAutoFree _autoFree(buf); String8 string; int cookie = 0x11111111; size_t actualSize; @@ -981,6 +977,7 @@ finished: if (err != NO_ERROR) { fprintf(stderr, "test_read_header_and_entity failed with %s\n", strerror(err)); } + free(buf); return err; } -- cgit v1.2.3-59-g8ed1b