1baa489faSSeongJae Park // SPDX-License-Identifier: GPL-2.0 2baa489faSSeongJae Park #include <string.h> 3baa489faSSeongJae Park #include <fcntl.h> 481b1e3f9SDavid Hildenbrand #include <dirent.h> 5c4277cb6SPeter Xu #include <sys/ioctl.h> 6c4277cb6SPeter Xu #include <linux/userfaultfd.h> 7600bca58SAndrei Vagin #include <linux/fs.h> 878391f64SPeter Xu #include <sys/syscall.h> 978391f64SPeter Xu #include <unistd.h> 10baa489faSSeongJae Park #include "../kselftest.h" 11baa489faSSeongJae Park #include "vm_util.h" 12baa489faSSeongJae Park 13baa489faSSeongJae Park #define PMD_SIZE_FILE_PATH "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size" 14baa489faSSeongJae Park #define SMAP_FILE_PATH "/proc/self/smaps" 15*391e8697SAlexander Zhu #define STATUS_FILE_PATH "/proc/self/status" 16baa489faSSeongJae Park #define MAX_LINE_LENGTH 500 17baa489faSSeongJae Park 18af605d26SPeter Xu unsigned int __page_size; 19af605d26SPeter Xu unsigned int __page_shift; 20af605d26SPeter Xu 21baa489faSSeongJae Park uint64_t pagemap_get_entry(int fd, char *start) 22baa489faSSeongJae Park { 23baa489faSSeongJae Park const unsigned long pfn = (unsigned long)start / getpagesize(); 24baa489faSSeongJae Park uint64_t entry; 25baa489faSSeongJae Park int ret; 26baa489faSSeongJae Park 27baa489faSSeongJae Park ret = pread(fd, &entry, sizeof(entry), pfn * sizeof(entry)); 28baa489faSSeongJae Park if (ret != sizeof(entry)) 29baa489faSSeongJae Park ksft_exit_fail_msg("reading pagemap failed\n"); 30baa489faSSeongJae Park return entry; 31baa489faSSeongJae Park } 32baa489faSSeongJae Park 33600bca58SAndrei Vagin static uint64_t __pagemap_scan_get_categories(int fd, char *start, struct page_region *r) 34600bca58SAndrei Vagin { 35600bca58SAndrei Vagin struct pm_scan_arg arg; 36600bca58SAndrei Vagin 37600bca58SAndrei Vagin arg.start = (uintptr_t)start; 38600bca58SAndrei Vagin arg.end = (uintptr_t)(start + psize()); 39600bca58SAndrei Vagin arg.vec = (uintptr_t)r; 40600bca58SAndrei Vagin arg.vec_len = 1; 41600bca58SAndrei Vagin arg.flags = 0; 42600bca58SAndrei Vagin arg.size = sizeof(struct pm_scan_arg); 43600bca58SAndrei Vagin arg.max_pages = 0; 44600bca58SAndrei Vagin arg.category_inverted = 0; 45600bca58SAndrei Vagin arg.category_mask = 0; 46600bca58SAndrei Vagin arg.category_anyof_mask = PAGE_IS_WPALLOWED | PAGE_IS_WRITTEN | PAGE_IS_FILE | 47600bca58SAndrei Vagin PAGE_IS_PRESENT | PAGE_IS_SWAPPED | PAGE_IS_PFNZERO | 48600bca58SAndrei Vagin PAGE_IS_HUGE | PAGE_IS_SOFT_DIRTY; 49600bca58SAndrei Vagin arg.return_mask = arg.category_anyof_mask; 50600bca58SAndrei Vagin 51600bca58SAndrei Vagin return ioctl(fd, PAGEMAP_SCAN, &arg); 52600bca58SAndrei Vagin } 53600bca58SAndrei Vagin 54600bca58SAndrei Vagin static uint64_t pagemap_scan_get_categories(int fd, char *start) 55600bca58SAndrei Vagin { 56600bca58SAndrei Vagin struct page_region r; 57600bca58SAndrei Vagin long ret; 58600bca58SAndrei Vagin 59600bca58SAndrei Vagin ret = __pagemap_scan_get_categories(fd, start, &r); 60600bca58SAndrei Vagin if (ret < 0) 61600bca58SAndrei Vagin ksft_exit_fail_msg("PAGEMAP_SCAN failed: %s\n", strerror(errno)); 62600bca58SAndrei Vagin if (ret == 0) 63600bca58SAndrei Vagin return 0; 64600bca58SAndrei Vagin return r.categories; 65600bca58SAndrei Vagin } 66600bca58SAndrei Vagin 67600bca58SAndrei Vagin /* `start` is any valid address. */ 68600bca58SAndrei Vagin static bool pagemap_scan_supported(int fd, char *start) 69600bca58SAndrei Vagin { 70600bca58SAndrei Vagin static int supported = -1; 71600bca58SAndrei Vagin int ret; 72600bca58SAndrei Vagin 73600bca58SAndrei Vagin if (supported != -1) 74600bca58SAndrei Vagin return supported; 75600bca58SAndrei Vagin 76600bca58SAndrei Vagin /* Provide an invalid address in order to trigger EFAULT. */ 77600bca58SAndrei Vagin ret = __pagemap_scan_get_categories(fd, start, (struct page_region *) ~0UL); 78600bca58SAndrei Vagin if (ret == 0) 79600bca58SAndrei Vagin ksft_exit_fail_msg("PAGEMAP_SCAN succeeded unexpectedly\n"); 80600bca58SAndrei Vagin 81600bca58SAndrei Vagin supported = errno == EFAULT; 82600bca58SAndrei Vagin 83600bca58SAndrei Vagin return supported; 84600bca58SAndrei Vagin } 85600bca58SAndrei Vagin 86600bca58SAndrei Vagin static bool page_entry_is(int fd, char *start, char *desc, 87600bca58SAndrei Vagin uint64_t pagemap_flags, uint64_t pagescan_flags) 88600bca58SAndrei Vagin { 89600bca58SAndrei Vagin bool m = pagemap_get_entry(fd, start) & pagemap_flags; 90600bca58SAndrei Vagin 91600bca58SAndrei Vagin if (pagemap_scan_supported(fd, start)) { 92600bca58SAndrei Vagin bool s = pagemap_scan_get_categories(fd, start) & pagescan_flags; 93600bca58SAndrei Vagin 94600bca58SAndrei Vagin if (m == s) 95600bca58SAndrei Vagin return m; 96600bca58SAndrei Vagin 97600bca58SAndrei Vagin ksft_exit_fail_msg( 98600bca58SAndrei Vagin "read and ioctl return unmatched results for %s: %d %d", desc, m, s); 99600bca58SAndrei Vagin } 100600bca58SAndrei Vagin return m; 101600bca58SAndrei Vagin } 102600bca58SAndrei Vagin 103baa489faSSeongJae Park bool pagemap_is_softdirty(int fd, char *start) 104baa489faSSeongJae Park { 105600bca58SAndrei Vagin return page_entry_is(fd, start, "soft-dirty", 106600bca58SAndrei Vagin PM_SOFT_DIRTY, PAGE_IS_SOFT_DIRTY); 107baa489faSSeongJae Park } 108baa489faSSeongJae Park 109baa489faSSeongJae Park bool pagemap_is_swapped(int fd, char *start) 110baa489faSSeongJae Park { 111600bca58SAndrei Vagin return page_entry_is(fd, start, "swap", PM_SWAP, PAGE_IS_SWAPPED); 112baa489faSSeongJae Park } 113baa489faSSeongJae Park 114baa489faSSeongJae Park bool pagemap_is_populated(int fd, char *start) 115baa489faSSeongJae Park { 116600bca58SAndrei Vagin return page_entry_is(fd, start, "populated", 117600bca58SAndrei Vagin PM_PRESENT | PM_SWAP, 118600bca58SAndrei Vagin PAGE_IS_PRESENT | PAGE_IS_SWAPPED); 119baa489faSSeongJae Park } 120baa489faSSeongJae Park 121baa489faSSeongJae Park unsigned long pagemap_get_pfn(int fd, char *start) 122baa489faSSeongJae Park { 123baa489faSSeongJae Park uint64_t entry = pagemap_get_entry(fd, start); 124baa489faSSeongJae Park 125baa489faSSeongJae Park /* If present (63th bit), PFN is at bit 0 -- 54. */ 1269f74696bSPeter Xu if (entry & PM_PRESENT) 127baa489faSSeongJae Park return entry & 0x007fffffffffffffull; 128baa489faSSeongJae Park return -1ul; 129baa489faSSeongJae Park } 130baa489faSSeongJae Park 131baa489faSSeongJae Park void clear_softdirty(void) 132baa489faSSeongJae Park { 133baa489faSSeongJae Park int ret; 134baa489faSSeongJae Park const char *ctrl = "4"; 135baa489faSSeongJae Park int fd = open("/proc/self/clear_refs", O_WRONLY); 136baa489faSSeongJae Park 137baa489faSSeongJae Park if (fd < 0) 138baa489faSSeongJae Park ksft_exit_fail_msg("opening clear_refs failed\n"); 139baa489faSSeongJae Park ret = write(fd, ctrl, strlen(ctrl)); 140baa489faSSeongJae Park close(fd); 141baa489faSSeongJae Park if (ret != strlen(ctrl)) 142baa489faSSeongJae Park ksft_exit_fail_msg("writing clear_refs failed\n"); 143baa489faSSeongJae Park } 144baa489faSSeongJae Park 145baa489faSSeongJae Park bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len) 146baa489faSSeongJae Park { 147baa489faSSeongJae Park while (fgets(buf, len, fp)) { 148baa489faSSeongJae Park if (!strncmp(buf, pattern, strlen(pattern))) 149baa489faSSeongJae Park return true; 150baa489faSSeongJae Park } 151baa489faSSeongJae Park return false; 152baa489faSSeongJae Park } 153baa489faSSeongJae Park 154baa489faSSeongJae Park uint64_t read_pmd_pagesize(void) 155baa489faSSeongJae Park { 156baa489faSSeongJae Park int fd; 157baa489faSSeongJae Park char buf[20]; 158baa489faSSeongJae Park ssize_t num_read; 159baa489faSSeongJae Park 160baa489faSSeongJae Park fd = open(PMD_SIZE_FILE_PATH, O_RDONLY); 161baa489faSSeongJae Park if (fd == -1) 162d6e61afbSDavid Hildenbrand return 0; 163baa489faSSeongJae Park 164baa489faSSeongJae Park num_read = read(fd, buf, 19); 165baa489faSSeongJae Park if (num_read < 1) { 166baa489faSSeongJae Park close(fd); 167d6e61afbSDavid Hildenbrand return 0; 168baa489faSSeongJae Park } 169baa489faSSeongJae Park buf[num_read] = '\0'; 170baa489faSSeongJae Park close(fd); 171baa489faSSeongJae Park 172baa489faSSeongJae Park return strtoul(buf, NULL, 10); 173baa489faSSeongJae Park } 174baa489faSSeongJae Park 175*391e8697SAlexander Zhu unsigned long rss_anon(void) 176*391e8697SAlexander Zhu { 177*391e8697SAlexander Zhu unsigned long rss_anon = 0; 178*391e8697SAlexander Zhu FILE *fp; 179*391e8697SAlexander Zhu char buffer[MAX_LINE_LENGTH]; 180*391e8697SAlexander Zhu 181*391e8697SAlexander Zhu fp = fopen(STATUS_FILE_PATH, "r"); 182*391e8697SAlexander Zhu if (!fp) 183*391e8697SAlexander Zhu ksft_exit_fail_msg("%s: Failed to open file %s\n", __func__, STATUS_FILE_PATH); 184*391e8697SAlexander Zhu 185*391e8697SAlexander Zhu if (!check_for_pattern(fp, "RssAnon:", buffer, sizeof(buffer))) 186*391e8697SAlexander Zhu goto err_out; 187*391e8697SAlexander Zhu 188*391e8697SAlexander Zhu if (sscanf(buffer, "RssAnon:%10lu kB", &rss_anon) != 1) 189*391e8697SAlexander Zhu ksft_exit_fail_msg("Reading status error\n"); 190*391e8697SAlexander Zhu 191*391e8697SAlexander Zhu err_out: 192*391e8697SAlexander Zhu fclose(fp); 193*391e8697SAlexander Zhu return rss_anon; 194*391e8697SAlexander Zhu } 195*391e8697SAlexander Zhu 196baa489faSSeongJae Park bool __check_huge(void *addr, char *pattern, int nr_hpages, 197baa489faSSeongJae Park uint64_t hpage_size) 198baa489faSSeongJae Park { 199baa489faSSeongJae Park uint64_t thp = -1; 200baa489faSSeongJae Park int ret; 201baa489faSSeongJae Park FILE *fp; 202baa489faSSeongJae Park char buffer[MAX_LINE_LENGTH]; 203baa489faSSeongJae Park char addr_pattern[MAX_LINE_LENGTH]; 204baa489faSSeongJae Park 205baa489faSSeongJae Park ret = snprintf(addr_pattern, MAX_LINE_LENGTH, "%08lx-", 206baa489faSSeongJae Park (unsigned long) addr); 207baa489faSSeongJae Park if (ret >= MAX_LINE_LENGTH) 208baa489faSSeongJae Park ksft_exit_fail_msg("%s: Pattern is too long\n", __func__); 209baa489faSSeongJae Park 210baa489faSSeongJae Park fp = fopen(SMAP_FILE_PATH, "r"); 211baa489faSSeongJae Park if (!fp) 212baa489faSSeongJae Park ksft_exit_fail_msg("%s: Failed to open file %s\n", __func__, SMAP_FILE_PATH); 213baa489faSSeongJae Park 214baa489faSSeongJae Park if (!check_for_pattern(fp, addr_pattern, buffer, sizeof(buffer))) 215baa489faSSeongJae Park goto err_out; 216baa489faSSeongJae Park 217baa489faSSeongJae Park /* 218baa489faSSeongJae Park * Fetch the pattern in the same block and check the number of 219baa489faSSeongJae Park * hugepages. 220baa489faSSeongJae Park */ 221baa489faSSeongJae Park if (!check_for_pattern(fp, pattern, buffer, sizeof(buffer))) 222baa489faSSeongJae Park goto err_out; 223baa489faSSeongJae Park 224baa489faSSeongJae Park snprintf(addr_pattern, MAX_LINE_LENGTH, "%s%%9ld kB", pattern); 225baa489faSSeongJae Park 226baa489faSSeongJae Park if (sscanf(buffer, addr_pattern, &thp) != 1) 227baa489faSSeongJae Park ksft_exit_fail_msg("Reading smap error\n"); 228baa489faSSeongJae Park 229baa489faSSeongJae Park err_out: 230baa489faSSeongJae Park fclose(fp); 231baa489faSSeongJae Park return thp == (nr_hpages * (hpage_size >> 10)); 232baa489faSSeongJae Park } 233baa489faSSeongJae Park 234baa489faSSeongJae Park bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size) 235baa489faSSeongJae Park { 236baa489faSSeongJae Park return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); 237baa489faSSeongJae Park } 238baa489faSSeongJae Park 239baa489faSSeongJae Park bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size) 240baa489faSSeongJae Park { 241baa489faSSeongJae Park return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); 242baa489faSSeongJae Park } 243baa489faSSeongJae Park 244baa489faSSeongJae Park bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size) 245baa489faSSeongJae Park { 246baa489faSSeongJae Park return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); 247baa489faSSeongJae Park } 248af605d26SPeter Xu 249af605d26SPeter Xu int64_t allocate_transhuge(void *ptr, int pagemap_fd) 250af605d26SPeter Xu { 251af605d26SPeter Xu uint64_t ent[2]; 252af605d26SPeter Xu 253af605d26SPeter Xu /* drop pmd */ 254af605d26SPeter Xu if (mmap(ptr, HPAGE_SIZE, PROT_READ | PROT_WRITE, 255af605d26SPeter Xu MAP_FIXED | MAP_ANONYMOUS | 256af605d26SPeter Xu MAP_NORESERVE | MAP_PRIVATE, -1, 0) != ptr) 257c811b0ceSMuhammad Usama Anjum ksft_exit_fail_msg("mmap transhuge\n"); 258af605d26SPeter Xu 259af605d26SPeter Xu if (madvise(ptr, HPAGE_SIZE, MADV_HUGEPAGE)) 260c811b0ceSMuhammad Usama Anjum ksft_exit_fail_msg("MADV_HUGEPAGE\n"); 261af605d26SPeter Xu 262af605d26SPeter Xu /* allocate transparent huge page */ 263af605d26SPeter Xu *(volatile void **)ptr = ptr; 264af605d26SPeter Xu 265af605d26SPeter Xu if (pread(pagemap_fd, ent, sizeof(ent), 266af605d26SPeter Xu (uintptr_t)ptr >> (pshift() - 3)) != sizeof(ent)) 267c811b0ceSMuhammad Usama Anjum ksft_exit_fail_msg("read pagemap\n"); 268af605d26SPeter Xu 269af605d26SPeter Xu if (PAGEMAP_PRESENT(ent[0]) && PAGEMAP_PRESENT(ent[1]) && 270af605d26SPeter Xu PAGEMAP_PFN(ent[0]) + 1 == PAGEMAP_PFN(ent[1]) && 271af605d26SPeter Xu !(PAGEMAP_PFN(ent[0]) & ((1 << (HPAGE_SHIFT - pshift())) - 1))) 272af605d26SPeter Xu return PAGEMAP_PFN(ent[0]); 273af605d26SPeter Xu 274af605d26SPeter Xu return -1; 275af605d26SPeter Xu } 276bd4d67e7SPeter Xu 277bd4d67e7SPeter Xu unsigned long default_huge_page_size(void) 278bd4d67e7SPeter Xu { 279bd4d67e7SPeter Xu unsigned long hps = 0; 280bd4d67e7SPeter Xu char *line = NULL; 281bd4d67e7SPeter Xu size_t linelen = 0; 282bd4d67e7SPeter Xu FILE *f = fopen("/proc/meminfo", "r"); 283bd4d67e7SPeter Xu 284bd4d67e7SPeter Xu if (!f) 285bd4d67e7SPeter Xu return 0; 286bd4d67e7SPeter Xu while (getline(&line, &linelen, f) > 0) { 287bd4d67e7SPeter Xu if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) { 288bd4d67e7SPeter Xu hps <<= 10; 289bd4d67e7SPeter Xu break; 290bd4d67e7SPeter Xu } 291bd4d67e7SPeter Xu } 292bd4d67e7SPeter Xu 293bd4d67e7SPeter Xu free(line); 294bd4d67e7SPeter Xu fclose(f); 295bd4d67e7SPeter Xu return hps; 296bd4d67e7SPeter Xu } 297c4277cb6SPeter Xu 29881b1e3f9SDavid Hildenbrand int detect_hugetlb_page_sizes(size_t sizes[], int max) 29981b1e3f9SDavid Hildenbrand { 30081b1e3f9SDavid Hildenbrand DIR *dir = opendir("/sys/kernel/mm/hugepages/"); 30181b1e3f9SDavid Hildenbrand int count = 0; 30281b1e3f9SDavid Hildenbrand 30381b1e3f9SDavid Hildenbrand if (!dir) 30481b1e3f9SDavid Hildenbrand return 0; 30581b1e3f9SDavid Hildenbrand 30681b1e3f9SDavid Hildenbrand while (count < max) { 30781b1e3f9SDavid Hildenbrand struct dirent *entry = readdir(dir); 30881b1e3f9SDavid Hildenbrand size_t kb; 30981b1e3f9SDavid Hildenbrand 31081b1e3f9SDavid Hildenbrand if (!entry) 31181b1e3f9SDavid Hildenbrand break; 31281b1e3f9SDavid Hildenbrand if (entry->d_type != DT_DIR) 31381b1e3f9SDavid Hildenbrand continue; 31481b1e3f9SDavid Hildenbrand if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1) 31581b1e3f9SDavid Hildenbrand continue; 31681b1e3f9SDavid Hildenbrand sizes[count++] = kb * 1024; 31781b1e3f9SDavid Hildenbrand ksft_print_msg("[INFO] detected hugetlb page size: %zu KiB\n", 31881b1e3f9SDavid Hildenbrand kb); 31981b1e3f9SDavid Hildenbrand } 32081b1e3f9SDavid Hildenbrand closedir(dir); 32181b1e3f9SDavid Hildenbrand return count; 32281b1e3f9SDavid Hildenbrand } 32381b1e3f9SDavid Hildenbrand 324c3315502SPeter Xu /* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */ 325c3315502SPeter Xu int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len, 326c3315502SPeter Xu bool miss, bool wp, bool minor, uint64_t *ioctls) 327c4277cb6SPeter Xu { 328c4277cb6SPeter Xu struct uffdio_register uffdio_register = { 0 }; 329c4277cb6SPeter Xu uint64_t mode = 0; 330c4277cb6SPeter Xu int ret = 0; 331c4277cb6SPeter Xu 332c4277cb6SPeter Xu if (miss) 333c4277cb6SPeter Xu mode |= UFFDIO_REGISTER_MODE_MISSING; 334c4277cb6SPeter Xu if (wp) 335c4277cb6SPeter Xu mode |= UFFDIO_REGISTER_MODE_WP; 336c4277cb6SPeter Xu if (minor) 337c4277cb6SPeter Xu mode |= UFFDIO_REGISTER_MODE_MINOR; 338c4277cb6SPeter Xu 339c4277cb6SPeter Xu uffdio_register.range.start = (unsigned long)addr; 340c4277cb6SPeter Xu uffdio_register.range.len = len; 341c4277cb6SPeter Xu uffdio_register.mode = mode; 342c4277cb6SPeter Xu 343c4277cb6SPeter Xu if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) 344c4277cb6SPeter Xu ret = -errno; 345c3315502SPeter Xu else if (ioctls) 346c3315502SPeter Xu *ioctls = uffdio_register.ioctls; 347c4277cb6SPeter Xu 348c4277cb6SPeter Xu return ret; 349c4277cb6SPeter Xu } 350c4277cb6SPeter Xu 351c3315502SPeter Xu int uffd_register(int uffd, void *addr, uint64_t len, 352c3315502SPeter Xu bool miss, bool wp, bool minor) 353c3315502SPeter Xu { 354c3315502SPeter Xu return uffd_register_with_ioctls(uffd, addr, len, 355c3315502SPeter Xu miss, wp, minor, NULL); 356c3315502SPeter Xu } 357c3315502SPeter Xu 358c4277cb6SPeter Xu int uffd_unregister(int uffd, void *addr, uint64_t len) 359c4277cb6SPeter Xu { 360c4277cb6SPeter Xu struct uffdio_range range = { .start = (uintptr_t)addr, .len = len }; 361c4277cb6SPeter Xu int ret = 0; 362c4277cb6SPeter Xu 363c4277cb6SPeter Xu if (ioctl(uffd, UFFDIO_UNREGISTER, &range) == -1) 364c4277cb6SPeter Xu ret = -errno; 365c4277cb6SPeter Xu 366c4277cb6SPeter Xu return ret; 367c4277cb6SPeter Xu } 368c8b90731SBreno Leitao 369c8b90731SBreno Leitao unsigned long get_free_hugepages(void) 370c8b90731SBreno Leitao { 371c8b90731SBreno Leitao unsigned long fhp = 0; 372c8b90731SBreno Leitao char *line = NULL; 373c8b90731SBreno Leitao size_t linelen = 0; 374c8b90731SBreno Leitao FILE *f = fopen("/proc/meminfo", "r"); 375c8b90731SBreno Leitao 376c8b90731SBreno Leitao if (!f) 377c8b90731SBreno Leitao return fhp; 378c8b90731SBreno Leitao while (getline(&line, &linelen, f) > 0) { 379c8b90731SBreno Leitao if (sscanf(line, "HugePages_Free: %lu", &fhp) == 1) 380c8b90731SBreno Leitao break; 381c8b90731SBreno Leitao } 382c8b90731SBreno Leitao 383c8b90731SBreno Leitao free(line); 384c8b90731SBreno Leitao fclose(f); 385c8b90731SBreno Leitao return fhp; 386c8b90731SBreno Leitao } 387