void build_superblock(superblock* superBlock, FILE* diskImage, unsigned long offset, bool verbose) { fseek(diskImage, SUPER_BLOCK_OFFSET + offset, SEEK_SET); fread(superBlock, sizeof(superblock), 1, diskImage); if (verbose) { print_superblock(superBlock); } if (superBlock->s_magic != MINIX_MAGIC) { fprintf(stderr, "Bad magic number. (%#x)\n", superBlock->s_magic); fprintf(stderr, "This doesn't look like a MINIX filesystem.\n"); exit(1); } }
int main(int argc, char **argv) { struct minix_superblock sb; unsigned long size = 0; size = get_file_size(); file_system = map2memory(size); assert(file_system != NULL); read_superblock(&sb); print_superblock(&sb); printf("first data zone is 0x%x\n", get_first_data_zone(sb)); directory_walk(&sb, get_first_data_zone(sb)); find_file_test(&sb); read_file_test(&sb); munmap(file_system, size); return 0; }
int ext2(void) { printf("Hello World, this is the Ext2 FS\n"); // Hardcode test fs into memory so that we can test read // Set up the superblock struct ext2_super_block *sb; sb = (struct ext2_super_block*) (VIRT_MEM_LOCATION + EXT2_SUPERBLOCK_LOCATION); sb->s_inodes_count = 50; sb->s_blocks_count = 8192; sb->s_r_blocks_count = 6; sb->s_free_blocks_count = 8186; sb->s_free_inodes_count = 49; sb->s_first_data_block = 1; sb->s_log_block_size = 0; sb->s_log_frag_size = 0; sb->s_blocks_per_group = 8192; sb->s_frags_per_group = 8192; sb->s_inodes_per_group = 50; sb->s_magic = EXT2_MAGIC; sb->s_state = EXT2_VALID_FS; sb->s_errors = EXT2_ERRORS_CONTINUE; sb->s_creator_os = EXT2_OS_XINU; sb->s_first_ino = 2; sb->s_inode_size = sizeof( struct ext2_inode ); sb->s_block_group_nr = 0; char name[16] = "FAKE RAM FS :D"; memcpy(sb->s_volume_name,name,16); // Set up the group descriptors table struct ext2_group_desc *gpd; // DUMB POINTER ARITHMATIC gpd = (struct ext2_group_desc *) (sb + 1); gpd->bg_block_bitmap = 2; gpd->bg_inode_bitmap = 3; gpd->bg_inode_table = 4; gpd->bg_free_blocks_count = 44; gpd->bg_free_inodes_count = 19; gpd->bg_used_dirs_count = 1; // Set up the block bitmap uint8 *blBitmap; blBitmap = (uint8 *) (sb + 2); blBitmap[0] = 0x3F; // super block int i; for (i = 6; i < sb->s_blocks_count; i++) blBitmap[i] = 0; // Set up the inode bitmap uint8 *iBitmap; iBitmap = (uint8 *) (sb + 3); iBitmap[0] = 0x1; // . for (i = 1; i < sb->s_inodes_count; i++) iBitmap[i] = 0; // Set up the inode table struct ext2_inode *iTbl; iTbl = (struct ext2_inode *) (sb + 4); // Set up . inode iTbl->i_mode = EXT2_S_IFDIR; iTbl->i_size = sizeof(struct ext2_dir_entry_2); iTbl->i_links_count = 0; iTbl->i_blocks = 1; iTbl->i_flags = EXT2_NODUMP_FL; iTbl->i_block[0] = 5; // Set up . entry for the home directory struct ext2_dir_entry_2 *blk5; blk5 = (struct ext2_dir_entry_2 *) (sb + 5); blk5->inode = 1; blk5->next_dirent = 0; blk5->name_len = 1; blk5->filetype = 2; char homeName[255] = "."; memcpy(blk5->name, homeName, 255); _fs_ext2_init(); touch1( xinu_fs, "./", "test" ); char bufferL[9] = "Go long!"; uint32 bytes_written; ext2_write_status stat = ext2_write_file_by_path( xinu_fs, "./test", bufferL, &bytes_written, 0, 8 ); touch1( xinu_fs, "./", "yo"); stat = ext2_write_file_by_path( xinu_fs, "./yo", bufferL, &bytes_written, 0, 8 ); touch1( xinu_fs, "./", "whoah" ); stat = ext2_write_file_by_path( xinu_fs, "./whoah", bufferL, &bytes_written, 0, 8 ); touch1( xinu_fs, "./", "iasjdf" ); stat = ext2_write_file_by_path( xinu_fs, "./iasjdf", bufferL, &bytes_written, 0, 8 ); touch1( xinu_fs, "./", "f" ); stat = ext2_write_file_by_path( xinu_fs, "./f", bufferL, &bytes_written, 0, 8 ); ls1( xinu_fs, "./" ); printf("removing yo\n"); rm1( xinu_fs, "./", "yo" ); ls1( xinu_fs, "./" ); printf("touching asdf\n"); touch1( xinu_fs, "./", "asdf" ); stat = ext2_write_file_by_path( xinu_fs, "./asdf", bufferL, &bytes_written, 0, 8 ); ls1( xinu_fs, "./" ); printf("mking dir\n"); mkdir1( xinu_fs, "./", "dir" ); ls1( xinu_fs, "./" ); printf("touching yo\n"); touch1( xinu_fs, "./dir/", "yo" ); ls1( xinu_fs, "./dir/"); copy1( xinu_fs, "./", "whoah", "./", "hello" ); ls1( xinu_fs, "./" ); cat1( xinu_fs, "./", "hello" ); copy1( xinu_fs, "./", "hello", "./dir/", "hello" ); printf("HERE\n"); ls1( xinu_fs, "./dir/" ); cat1( xinu_fs, "./dir/", "hello" ); printf("removeing\n"); mv1( xinu_fs, "./dir/", "yo", "./", "a" ); ls1( xinu_fs, "./" ); cat1( xinu_fs, "./", "a" ); #if 0 // Test the read/write functions printf("Testing hardcoded data\n"); print_superblock( xinu_fs->sb ); struct ext2_inode *i1 = ext2_get_inode(xinu_fs, 1); print_inode( i1, 1, xinu_fs ); struct ext2_dir_entry_2 *home = ext2_get_first_dirent(xinu_fs, i1 ); print_dirent( home ); uint32 inode_num = ext2_inode_alloc( xinu_fs ); struct ext2_inode *i2 = ext2_get_inode( xinu_fs, inode_num+1 ); i2->i_mode = EXT2_S_IFREG; i2->i_size = 0; printf("Allocated new inode\n"); print_inode( i2, inode_num+1, xinu_fs ); struct ext2_dir_entry_2 *dirent = ext2_dirent_alloc( xinu_fs, i1 ); dirent->inode = 2; dirent->next_dirent = 0; dirent->name_len = 4; dirent->filetype = EXT2_FT_REG_FILE; char testName[255] = "test"; memcpy(dirent->name, testName, 255); printf("Allocated new dir_entry_2 test\n"); print_dirent( dirent ); char path[8] = "./test"; char buffer[14] = "Writing! Yay!"; char bufferL[9] = "Go long!"; uint32 bytes_written; ext2_write_status stat = ext2_write_file_by_path( xinu_fs, path, buffer, &bytes_written, 0, 13 ); printf("bytes_written = %d stat = %d\n", bytes_written, stat); char buffer2[12*1024]; // stat = ext2_write_file_by_path( xinu_fs, path, buffer2, // &bytes_written, 13, (12*1024)-1 ); printf("bytes_written = %d stat = %d\n", bytes_written, stat); // stat = ext2_write_file_by_path( xinu_fs, path, bufferL, // &bytes_written, (12*1024)+12, 8 ); printf("bytes_written = %d stat = %d\n", bytes_written, stat); int read = 0; char readBuf[30]; read = ext2_read_dirent( xinu_fs, dirent, readBuf, 0, 29); printf("Read %d bytes readBuf = %s\n", read, readBuf); // read = ext2_read_dirent( xinu_fs, dirent, readBuf, (12*1024)+12, 10); // printf("Read %d bytes readBuf = %s\n", read, readBuf); #endif return 0; }
int main(int argc, char **argv) { int rc; char *endptr; char *dev_path; service_id_t service_id; ext2_filesystem_t filesystem; int arg_flags; uint32_t inode = 0; uint32_t inode_data = 0; arg_flags = 0; if (argc < 2) { printf(NAME ": Error, argument missing.\n"); syntax_print(); return 1; } /* Skip program name */ --argc; ++argv; if (argc > 0 && str_cmp(*argv, "--no-check") == 0) { --argc; ++argv; arg_flags |= ARG_NO_CHECK; } if (argc > 0 && str_cmp(*argv, "--superblock") == 0) { --argc; ++argv; arg_flags |= ARG_SUPERBLOCK; } if (argc > 0 && str_cmp(*argv, "--block-groups") == 0) { --argc; ++argv; arg_flags |= ARG_BLOCK_GROUPS; } if (argc > 0 && str_cmp(*argv, "--inode") == 0) { --argc; ++argv; if (argc == 0) { printf(NAME ": Argument expected for --inode\n"); return 2; } inode = strtol(*argv, &endptr, 10); if (*endptr != '\0') { printf(NAME ": Error, invalid argument for --inode.\n"); syntax_print(); return 1; } arg_flags |= ARG_INODE; --argc; ++argv; if (argc > 0 && str_cmp(*argv, "--blocks") == 0) { --argc; ++argv; arg_flags |= ARG_INODE_BLOCKS; } if (argc > 0 && str_cmp(*argv, "--data") == 0) { --argc; ++argv; if (argc == 0) { printf(NAME ": Argument expected for --data\n"); return 2; } inode_data = strtol(*argv, &endptr, 10); if (*endptr != '\0') { printf(NAME ": Error, invalid argument for --data.\n"); syntax_print(); return 1; } arg_flags |= ARG_INODE_DATA; --argc; ++argv; } if (argc > 0 && str_cmp(*argv, "--list") == 0) { --argc; ++argv; arg_flags |= ARG_INODE_LIST; } } if (argc < 1) { printf(NAME ": Error, argument missing.\n"); syntax_print(); return 1; } else if (argc > 1) { printf(NAME ": Error, unexpected argument.\n"); syntax_print(); return 1; } assert(argc == 1); /* Display common things by default */ if ((arg_flags & ARG_ALL) == 0) { arg_flags = ARG_COMMON; } dev_path = *argv; rc = loc_service_get_id(dev_path, &service_id, 0); if (rc != EOK) { printf(NAME ": Error resolving device `%s'.\n", dev_path); return 2; } rc = ext2_filesystem_init(&filesystem, service_id); if (rc != EOK) { printf(NAME ": Error initializing libext2.\n"); return 3; } rc = ext2_filesystem_check_sanity(&filesystem); if (rc != EOK) { printf(NAME ": Filesystem did not pass sanity check.\n"); if (!(arg_flags & ARG_NO_CHECK)) { return 3; } } if (arg_flags & ARG_SUPERBLOCK) { print_superblock(filesystem.superblock); } if (arg_flags & ARG_BLOCK_GROUPS) { print_block_groups(&filesystem); } if (arg_flags & ARG_INODE) { print_inode_by_number(&filesystem, inode, arg_flags & ARG_INODE_DATA, inode_data, arg_flags & ARG_INODE_LIST, arg_flags & ARG_INODE_BLOCKS); } ext2_filesystem_fini(&filesystem); return 0; }
int main(int argc, char const *argv[]) { // Check for arguments if (argc < 8) { exit_error_f( "Usage:\n" "%s DEV BLOCK_SIZE JB_TRANSACTIONS HASH_TYPE SALT HMAC_TYPE SECRET lazy|nolazy\n" "%s MINT_DEV DATA_DEV BLOCK_SIZE JB_TRANSACTIONS HASH_TYPE SALT HMAC_TYPE SECRET lazy|nolazy\n", argv[0], argv[0]); } const char *dev, *dev2, *hash_type, *hmac_type, *salt_str, *secret_str; uint32_t block_size, journal_blocks; bool zero; if (!strcmp(argv[argc - 1], "lazy")) { zero = false; } else if (!strcmp(argv[argc - 1], "nolazy")) { zero = true; } else { exit_error_f("Unsupported optional argument: %s", argv[argc - 1]); } bool two_disks = (argc == 10); dev = argv[1]; dev2 = argv[2]; hash_type = argv[4 + two_disks]; salt_str = argv[5 + two_disks]; hmac_type = argv[6 + two_disks]; secret_str = argv[7 + two_disks]; // Open destination device int file, file2; if ((file = open(dev, O_RDWR)) < 0) { exit_error_f("Could not open: '%s' for writing, %s", dev, strerror(errno)); } // Get size // TODO: size of file in 512 chunks? struct stat file_stats, file_stats2; if (fstat(file, &file_stats) != 0) { exit_error_f("Could not get file stats for: '%s', %s", dev, strerror(errno)); } if (!(S_ISREG(file_stats.st_mode) || S_ISBLK(file_stats.st_mode))) { exit_error_f("File is neither a regular file nor block device"); } if (two_disks) { if ((file2 = open(dev2, O_RDWR)) < 0) { exit_error_f("Could not open: '%s' for writing, %s", dev2, strerror(errno)); } // Get size // TODO: size of file in 512 chunks? if (fstat(file2, &file_stats2) != 0) { exit_error_f("Could not get file stats for: '%s', %s", dev2, strerror(errno)); } if (!(S_ISREG(file_stats2.st_mode) || S_ISBLK(file_stats2.st_mode))) { exit_error_f("File is neither a regular file nor block device"); } } // Get block size if (sscanf(argv[2 + two_disks], "%u", &block_size) != 1) { exit_error_f("Invalid block size: '%s'", argv[2 + two_disks]); } if (block_size < 512) { exit_error_f("Invalid block size: '%u' < 512", block_size); } // Remainder check if (S_ISREG(file_stats.st_mode) && file_stats.st_size % block_size != 0) { warn("File is not a multiple of block_size: %d. %ju bytes left over", block_size, file_stats.st_size % block_size); } if (two_disks && S_ISREG(file_stats2.st_mode) && file_stats2.st_size % block_size != 0) { warn("File is not a multiple of block_size: %d. %ju bytes left over", block_size, file_stats.st_size % block_size); } // Number of journal blocks if (sscanf(argv[3 + two_disks], "%u", &journal_blocks) != 1) { exit_error_f("Invalid journal blocks number: '%s'", argv[3 + two_disks]); } OpenSSL_add_all_digests(); // Block hash algorithm EVP_MD_CTX *mdctx_hash = EVP_MD_CTX_create(); const EVP_MD *md_hash; md_hash = EVP_get_digestbyname(hash_type); if (!md_hash) { exit_error_f("Unsupported hash type: %s", hash_type); } uint32_t hash_bytes = EVP_MD_size(md_hash); // Hmac algorithm const EVP_MD *md_hmac; md_hmac = EVP_get_digestbyname(hmac_type); if (!md_hmac) { exit_error_f("Unsupported hmac type: %s", hmac_type); } // Parse and check salt char salt[128]; if (strlen(salt_str) % 2 != 0) { exit_error_f("Invalid hex salt: length not a multiple of 2"); } if (strlen(salt_str) > 256) { exit_error_f("Salt is too long. %lu > %d", strlen(salt_str), 256); } if (hex_to_bytes(salt_str, strlen(salt_str), (char*)salt) != 0) { exit_error_f("Invalid hex salt: '%s'", salt_str); } // Parse and check secrets char secret[hash_bytes]; if (strlen(secret_str) % 2 != 0) { exit_error_f("Invalid hex secret: length not a multiple of 2"); } if (hex_to_bytes(secret_str, strlen(secret_str), (char*)secret) != 0) { exit_error_f("Invalid hex inner pad: '%s'", secret_str); } // Calculate data size, hash block size, journal size // TODO: uh...this is 64 bits... uint64_t data_blocks = 0; uint32_t hash_blocks = 0; uint32_t jb_blocks = 0; uint32_t pad_blocks = 0; uint32_t *blocks_per_level = malloc(sizeof(uint32_t) * DM_MINTEGRITY_MAX_LEVELS); uint32_t levels = 0; uint64_t blocks, blocks2; if (S_ISREG(file_stats.st_mode)) { blocks = file_stats.st_size / block_size; } else if (S_ISBLK(file_stats.st_mode)) { if(ioctl(file, BLKGETSIZE64, &blocks) != 0){ exit_error_f("ioctl for block size failed: %s", strerror(errno)); } blocks = blocks / block_size; } if (two_disks) { if (S_ISREG(file_stats2.st_mode)) { blocks2 = file_stats2.st_size / block_size; } else if (S_ISBLK(file_stats2.st_mode)) { if(ioctl(file2, BLKGETSIZE64, &blocks2) != 0){ exit_error_f("ioctl for block size failed: %s", strerror(errno)); } blocks2 = blocks2 / block_size; } } // Fanout uint8_t fls, pls = 0; uint32_t fanout = block_size / hash_bytes; while (fanout > 0) { if ((fanout & 1) == 1) { fls = pls; } pls ++; fanout = fanout >> 1; } fanout = 1 << fls; // Use up entire block device if (!two_disks) { compute_block_numbers(blocks, block_size, fanout, journal_blocks, &data_blocks, &hash_blocks, &jb_blocks, &pad_blocks, &levels, blocks_per_level, hash_bytes); } else { jb_blocks = journal_blocks; data_blocks = blocks2; compute_hash_blocks(blocks2, fanout, &levels, &hash_blocks, blocks_per_level); if (hash_blocks + journal_blocks > blocks2) { exit_error_f("Need: %u hash + journal blocks, but %s only has %ju", hash_blocks + journal_blocks, dev, blocks); } } // Result info info("Blocks: %ju = Superblock: 1, Data: %ju, Hash: %u, JB: %u, Pad: %u, Levels: %u", blocks, data_blocks, hash_blocks, jb_blocks, pad_blocks, levels); // Calculate each hash block level char **hash_levels = (char**)malloc(sizeof(char*) * levels); char hash_output[EVP_MAX_MD_SIZE]; uint32_t hash_length; char *zero_block = (char*)malloc(block_size); char *temp_block = (char*)malloc(block_size); bzero(zero_block, block_size); char buf[128]; // Data hash hash(md_hash, mdctx_hash, zero_block, block_size, salt, strlen(salt_str) / 2, hash_output, &hash_length); // Now loop through each level for (uint32_t i = 0; i < levels; i++) { hash_levels[i] = (char*)malloc(block_size); // Fill block with hashes - padding is zeros bzero(hash_levels[i], block_size); for (uint32_t f = 0; f < fanout; f++) { for (int b = 0; b < hash_bytes; b++) { hash_levels[i][f * (block_size / (1 << fls)) + b] = hash_output[b]; } } // Compute hash of this level for next iteration/root hash(md_hash, mdctx_hash, hash_levels[i], block_size, salt, strlen(salt_str) / 2, hash_output, &hash_length); } // Write out hash superblock struct mint_superblock *msb = malloc(sizeof(struct mint_superblock)); // Zero out everything bzero(msb, sizeof(struct mint_superblock)); // Magic msb->magic = 0x796c694c; // Version msb->version = 1; // Make a new uuid! uuid_t uuid; uuid_generate(uuid); // TODO: is there a better way of doing this? memcpy(&msb->uuid, &uuid, 16); // Copy hash algorithm name strcpy(msb->hash_algorithm, hash_type); // Copy hmac algorithm name strcpy(msb->hmac_algorithm, hmac_type); // Block size! msb->block_size = block_size; // Set block numbers msb->data_blocks = data_blocks; msb->hash_blocks = hash_blocks; msb->jb_blocks = jb_blocks; // Set salt size msb->salt_size = strlen(salt_str) / 2; // Copy salt memcpy(msb->salt, salt, msb->salt_size); // Set root hash memcpy(msb->root, hash_output, hash_length); // Write it out! if (write(file, msb, sizeof(struct mint_superblock)) < 0) { exit_error_f("Failed to write MSB: %s", strerror(errno)); } if (write(file, zero_block, block_size - 512) < 0) { exit_error_f("Failed to write MSB pad: %s", strerror(errno)); } // Big block buffer uint32_t multiple = 1024; char *big_block = (char*)malloc(block_size * multiple); bzero(big_block, block_size * multiple); // Write out hash block levels uint8_t p = 0; info("Writing hash blocks..."); uint32_t h_written = 1; for (int i = levels - 1; i >= 0; i--) { // Copy into big buffer for (uint32_t m = 0; m < multiple; m++) { memcpy(big_block + m * block_size, hash_levels[i], block_size); } // Write out big buffer for (uint32_t j = 0; j < blocks_per_level[i] / multiple; j++) { h_written += multiple; p = progress(h_written, hash_blocks, 79, p); if(write(file, big_block, block_size * multiple) < 0){ exit_error_f("Failed to write hash block: %u, %s", h_written - 1, strerror(errno)); } } for (uint32_t j = 0; j < blocks_per_level[i] % multiple; j++) { p = progress(h_written++, hash_blocks, 79, p); if(write(file, hash_levels[i], block_size) < 0){ exit_error_f("Failed to write hash block: %u, %s", h_written - 1, strerror(errno)); } } } fprintf(stderr, "\n"); // Initialize journal struct mint_journal_superblock *mjsb = (struct mint_journal_superblock*) malloc(sizeof(struct mint_journal_superblock)); bzero(mjsb, sizeof(struct mint_journal_superblock)); // Magic mjsb->header.magic = MJ_MAGIC; // Superblock mjsb->header.type = TYPE_MJSB; // Number of blocks mjsb->blocks = jb_blocks; // Head, tail, and fill are 0 mjsb->head = 0; mjsb->tail = 0; mjsb->fill = 0; mjsb->sequence = 0; // Clean mjsb->state = 0; info("Writing journal..."); if (write(file, mjsb, sizeof(struct mint_journal_superblock)) < 0) { exit_error_f("Failed to write journal superblock:, %s", strerror(errno)); } if (write(file, zero_block, block_size - 512) < 0) { exit_error_f("Failed to write journal superblock pad: %s", strerror(errno)); } struct mint_journal_header *mjh = (struct mint_journal_header*) malloc(sizeof(struct mint_journal_header)); bzero(mjh, sizeof(struct mint_journal_header)); // Magic mjh->magic = MJ_MAGIC; // Nothing block mjh->type = TYPE_MJNB; // Copy headers into start of every block bzero(big_block, block_size * multiple); for (uint64_t i = 0; i < multiple; i++) { memcpy(big_block + i * block_size, mjh, sizeof(struct mint_journal_header)); } p = 0; for (uint64_t i = 0; i < (jb_blocks - 1) / multiple; i++) { if(write(file, big_block, block_size * multiple) < 0){ exit_error_f("Failed to write journal block: %ju, %s", i, strerror(errno)); } p = progress(i * multiple + 1, jb_blocks, 79, p); } for (uint64_t i = 0; i < (jb_blocks - 1) % multiple; i++) { if(write(file, big_block, block_size) < 0){ exit_error_f("Failed to write journal block: %ju, %s", i, strerror(errno)); } p = progress(jb_blocks - ((jb_blocks - 1) % multiple) + i + 2, jb_blocks, 79, p); } fprintf(stderr, "\n"); // Zero out data if (zero) { int f = two_disks ? file2 : file; bzero(big_block, block_size * multiple); info("Writing data blocks..."); p = 0; for (uint64_t i = 0; i < data_blocks / multiple; i++) { if(write(f, big_block, block_size * multiple) < 0){ exit_error_f("Failed to write data block: %ju, %s", i, strerror(errno)); } p = progress(i * multiple, data_blocks, 79, p); } for (uint64_t i = 0; i < data_blocks % multiple; i++) { if(write(f, zero_block, block_size) < 0){ exit_error_f("Failed to write data block: %ju, %s", i, strerror(errno)); } p = progress(data_blocks - (data_blocks % multiple) + i + 1, data_blocks, 79, p); } fprintf(stderr, "\n"); } else { info("Skipping disk zeroing..."); } close(file); if (two_disks) { close(file2); } print_superblock(msb); bytes_to_hex(msb->root, hash_bytes, buf); printf("dmsetup create meow --table \"%u %ju mintegrity %s%s%s %u %u %u %ju " "%s %s %s %s %s%s\"\n", 0, // Start is 0 data_blocks * (block_size / 512), // Size of device given to device mapper // Mintegrity options dev, // String of block device two_disks ? " " : "", two_disks ? dev2 : "", block_size, // Block size hash_blocks, // Number of hash blocks jb_blocks, // Number of journaling blocks data_blocks, // Number of data blocks hash_type, // Hash type buf, // Root digest to verity salt_str, // Salt hmac_type, // Hash type for hmac secret_str, // Hmac secret zero ? "" : " lazy" ); free(mjh); free(mjsb); free(msb); free(blocks_per_level); free(zero_block); free(big_block); free(temp_block); for (int i = 0; i < levels; i++) { free(hash_levels[i]); } free(hash_levels); EVP_MD_CTX_destroy(mdctx_hash); return 0; }