diff options
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | fake-msi.c | 183 | ||||
| -rw-r--r-- | version.c | 196 |
3 files changed, 197 insertions, 184 deletions
diff --git a/Makefile.am b/Makefile.am index cc11cbd..668425e 100644 --- a/Makefile.am +++ b/Makefile.am | |||
| @@ -8,7 +8,7 @@ libwinterop_so_la_SOURCES = fake-winterop.c fake-lib.c fake-lib.h \ | |||
| 8 | memory.c memory.h | 8 | memory.c memory.h |
| 9 | 9 | ||
| 10 | libmsi_so_la_SOURCES = fake-msi.c fake-lib.c fake-lib.h md5.c \ | 10 | libmsi_so_la_SOURCES = fake-msi.c fake-lib.c fake-lib.h md5.c \ |
| 11 | memory.c memory.h | 11 | memory.c memory.h version.c |
| 12 | 12 | ||
| 13 | libpreload_la_SOURCES = preload.c | 13 | libpreload_la_SOURCES = preload.c |
| 14 | libpreload_la_LDFLAGS = -ldl | 14 | libpreload_la_LDFLAGS = -ldl |
| @@ -17,189 +17,6 @@ | |||
| 17 | #include "memory.h" | 17 | #include "memory.h" |
| 18 | #include "fake-lib.h" | 18 | #include "fake-lib.h" |
| 19 | 19 | ||
| 20 | uint32_t MsiGetFileVersionW(const char16_t *filename, | ||
| 21 | char16_t *version, uint32_t *version_size, | ||
| 22 | char16_t *language, uint32_t *language_size) | ||
| 23 | { | ||
| 24 | char *fname = ascii(filename, true); | ||
| 25 | uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */ | ||
| 26 | int fd = -1; | ||
| 27 | void *mapv = MAP_FAILED; | ||
| 28 | |||
| 29 | if (version && *version_size) | ||
| 30 | *version = 0; | ||
| 31 | if (language && *language_size) | ||
| 32 | *language = 0; | ||
| 33 | |||
| 34 | fd = open(fname, O_RDONLY); | ||
| 35 | if (fd < 0) | ||
| 36 | err(1, "%s: open", fname); | ||
| 37 | struct stat st; | ||
| 38 | if (fstat(fd, &st) < 0) | ||
| 39 | err(1, "%s: fstat", fname); | ||
| 40 | size_t fsize = st.st_size; | ||
| 41 | mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0); | ||
| 42 | if (mapv == MAP_FAILED) | ||
| 43 | err(1, "%s: mmap", fname); | ||
| 44 | unsigned char *map = (unsigned char *)mapv; | ||
| 45 | |||
| 46 | if (le(map, fsize, 0, 2) != (('Z'<<8) | 'M')) { | ||
| 47 | warnx("MsiGetFileInfo(%s) -> no MZ", fname); | ||
| 48 | goto cleanup; | ||
| 49 | } | ||
| 50 | unsigned pe_pos = le(map, fsize, 0x3c, 4); | ||
| 51 | if (le(map, fsize, pe_pos, 4) != (('E'<<8) | 'P')) { | ||
| 52 | warnx("MsiGetFileInfo(%s) -> no PE", fname); | ||
| 53 | goto cleanup; | ||
| 54 | } | ||
| 55 | pe_pos += 4; /* skip to the main header */ | ||
| 56 | unsigned nsections = le(map, fsize, pe_pos + 2, 2); | ||
| 57 | unsigned opthdr_size = le(map, fsize, pe_pos + 16, 2); | ||
| 58 | unsigned opthdr_pos = pe_pos + 20; | ||
| 59 | /* bool sixtyfourbit = le(map, fsize, opthdr_pos, 2) == 0x020B; */ | ||
| 60 | unsigned secthdr_pos = opthdr_pos + opthdr_size; | ||
| 61 | while (nsections > 0) { | ||
| 62 | if (le(map, fsize, secthdr_pos+0, 1) == '.' && | ||
| 63 | le(map, fsize, secthdr_pos+1, 1) == 'r' && | ||
| 64 | le(map, fsize, secthdr_pos+2, 1) == 's' && | ||
| 65 | le(map, fsize, secthdr_pos+3, 1) == 'r' && | ||
| 66 | le(map, fsize, secthdr_pos+4, 1) == 'c' && | ||
| 67 | le(map, fsize, secthdr_pos+5, 1) == 0) | ||
| 68 | goto found_resource_section; | ||
| 69 | secthdr_pos += 0x28; | ||
| 70 | nsections--; | ||
| 71 | } | ||
| 72 | warnx("MsiGetFileInfo(%s) -> no .rsrc", fname); | ||
| 73 | goto cleanup; | ||
| 74 | |||
| 75 | found_resource_section:; | ||
| 76 | unsigned rsrc_size = le(map, fsize, secthdr_pos+8, 4); | ||
| 77 | unsigned rsrc_offset = le(map, fsize, secthdr_pos+20, 4); | ||
| 78 | unsigned rsrc_vaddr = le(map, fsize, secthdr_pos+12, 4); | ||
| 79 | |||
| 80 | unsigned res_dir_offset = rsrc_offset; | ||
| 81 | unsigned nnamed, nid; | ||
| 82 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 83 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 84 | for (unsigned i = nnamed; i < nnamed+nid; i++) { | ||
| 85 | unsigned id = le(map, fsize, res_dir_offset + 16 + 8*i, 4); | ||
| 86 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 87 | if (id == 16 && (entry & 0x80000000)) { | ||
| 88 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 89 | goto found_versioninfo_toplevel; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname); | ||
| 93 | goto cleanup; | ||
| 94 | |||
| 95 | found_versioninfo_toplevel: | ||
| 96 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 97 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 98 | for (unsigned i = 0; i < nnamed+nid; i++) { | ||
| 99 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 100 | if (entry & 0x80000000) { | ||
| 101 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 102 | goto found_versioninfo_2ndlevel; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname); | ||
| 106 | goto cleanup; | ||
| 107 | |||
| 108 | found_versioninfo_2ndlevel: | ||
| 109 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 110 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 111 | for (unsigned i = 0; i < nnamed+nid; i++) { | ||
| 112 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 113 | if (!(entry & 0x80000000)) { | ||
| 114 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 115 | goto found_versioninfo_3rdlevel; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname); | ||
| 119 | goto cleanup; | ||
| 120 | |||
| 121 | found_versioninfo_3rdlevel:; | ||
| 122 | unsigned versioninfo_offset = le(map, fsize, res_dir_offset, 4); | ||
| 123 | unsigned versioninfo_size = le(map, fsize, res_dir_offset+4, 4); | ||
| 124 | versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr; | ||
| 125 | |||
| 126 | unsigned name_offset = versioninfo_offset + 6; | ||
| 127 | const char *next_name_chr = "VS_VERSION_INFO"; | ||
| 128 | do { | ||
| 129 | if (le(map, fsize, name_offset, 2) != *next_name_chr) | ||
| 130 | goto cleanup; /* identifying string didn't match */ | ||
| 131 | name_offset += 2; | ||
| 132 | } while (*next_name_chr++); | ||
| 133 | unsigned fixed_offset = (name_offset + 3) & ~3; | ||
| 134 | if (le(map, fsize, fixed_offset, 4) != 0xFEEF04BDU) { | ||
| 135 | warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname); | ||
| 136 | goto cleanup; | ||
| 137 | } | ||
| 138 | int four_part_version[4]; | ||
| 139 | four_part_version[0] = le(map, fsize, fixed_offset + 10, 2); | ||
| 140 | four_part_version[1] = le(map, fsize, fixed_offset + 8, 2); | ||
| 141 | four_part_version[2] = le(map, fsize, fixed_offset + 14, 2); | ||
| 142 | four_part_version[3] = le(map, fsize, fixed_offset + 12, 2); | ||
| 143 | unsigned child_offset = fixed_offset + | ||
| 144 | le(map, fsize, versioninfo_offset+2, 2); | ||
| 145 | unsigned lcid; | ||
| 146 | while (child_offset < versioninfo_offset + versioninfo_size) { | ||
| 147 | unsigned this_child_offset = child_offset; | ||
| 148 | child_offset += le(map, fsize, child_offset, 2); | ||
| 149 | child_offset = (child_offset + 3) &~ 3; | ||
| 150 | if (child_offset <= this_child_offset) { | ||
| 151 | warnx("MsiGetFileInfo(%s) -> bad length field", fname); | ||
| 152 | goto cleanup; | ||
| 153 | } | ||
| 154 | const char *next_name_chr = "VarFileInfo"; | ||
| 155 | name_offset = this_child_offset + 6; | ||
| 156 | do { | ||
| 157 | if (le(map, fsize, name_offset, 2) != *next_name_chr) | ||
| 158 | goto this_is_not_a_varfileinfo; | ||
| 159 | name_offset += 2; | ||
| 160 | } while (*next_name_chr++); | ||
| 161 | unsigned subchild_offset = (name_offset + 3) & ~3; | ||
| 162 | |||
| 163 | next_name_chr = "Translation"; | ||
| 164 | name_offset = subchild_offset + 6; | ||
| 165 | do { | ||
| 166 | if (le(map, fsize, name_offset, 2) != *next_name_chr) { | ||
| 167 | warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname); | ||
| 168 | goto cleanup; | ||
| 169 | } | ||
| 170 | name_offset += 2; | ||
| 171 | } while (*next_name_chr++); | ||
| 172 | subchild_offset = (name_offset + 3) & ~3; | ||
| 173 | lcid = le(map, fsize, subchild_offset, 2); | ||
| 174 | goto success; | ||
| 175 | this_is_not_a_varfileinfo: | ||
| 176 | continue; | ||
| 177 | } | ||
| 178 | warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname); | ||
| 179 | goto cleanup; | ||
| 180 | |||
| 181 | success:; | ||
| 182 | char verbuf[256], langbuf[256]; | ||
| 183 | snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d", | ||
| 184 | four_part_version[0], four_part_version[1], | ||
| 185 | four_part_version[2], four_part_version[3]); | ||
| 186 | snprintf(langbuf, sizeof(langbuf), "%u", lcid); | ||
| 187 | warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf); | ||
| 188 | if (version) | ||
| 189 | c16cpy(version, version_size, verbuf); | ||
| 190 | if (language) | ||
| 191 | c16cpy(language, language_size, langbuf); | ||
| 192 | toret = 0; | ||
| 193 | |||
| 194 | cleanup: | ||
| 195 | if (mapv != MAP_FAILED) | ||
| 196 | munmap(mapv, fsize); | ||
| 197 | if (fd != -1) | ||
| 198 | close(fd); | ||
| 199 | sfree(fname); | ||
| 200 | return toret; | ||
| 201 | } | ||
| 202 | |||
| 203 | typedef struct MsiTypePrefix { | 20 | typedef struct MsiTypePrefix { |
| 204 | enum { MAIN, VIEW, RECORD } type; | 21 | enum { MAIN, VIEW, RECORD } type; |
| 205 | } MsiTypePrefix; | 22 | } MsiTypePrefix; |
diff --git a/version.c b/version.c new file mode 100644 index 0000000..dd62fc4 --- /dev/null +++ b/version.c | |||
| @@ -0,0 +1,196 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdint.h> | ||
| 3 | #include <stdbool.h> | ||
| 4 | |||
| 5 | #include <fcntl.h> | ||
| 6 | #include <sys/mman.h> | ||
| 7 | #include <sys/types.h> | ||
| 8 | #include <sys/stat.h> | ||
| 9 | #include <unistd.h> | ||
| 10 | #include <err.h> | ||
| 11 | |||
| 12 | #include "memory.h" | ||
| 13 | #include "fake-lib.h" | ||
| 14 | |||
| 15 | uint32_t MsiGetFileVersionW(const char16_t *filename, | ||
| 16 | char16_t *version, uint32_t *version_size, | ||
| 17 | char16_t *language, uint32_t *language_size) | ||
| 18 | { | ||
| 19 | char *fname = ascii(filename, true); | ||
| 20 | uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */ | ||
| 21 | int fd = -1; | ||
| 22 | void *mapv = MAP_FAILED; | ||
| 23 | |||
| 24 | if (version && *version_size) | ||
| 25 | *version = 0; | ||
| 26 | if (language && *language_size) | ||
| 27 | *language = 0; | ||
| 28 | |||
| 29 | fd = open(fname, O_RDONLY); | ||
| 30 | if (fd < 0) | ||
| 31 | err(1, "%s: open", fname); | ||
| 32 | struct stat st; | ||
| 33 | if (fstat(fd, &st) < 0) | ||
| 34 | err(1, "%s: fstat", fname); | ||
| 35 | size_t fsize = st.st_size; | ||
| 36 | mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0); | ||
| 37 | if (mapv == MAP_FAILED) | ||
| 38 | err(1, "%s: mmap", fname); | ||
| 39 | unsigned char *map = (unsigned char *)mapv; | ||
| 40 | |||
| 41 | if (le(map, fsize, 0, 2) != (('Z'<<8) | 'M')) { | ||
| 42 | warnx("MsiGetFileInfo(%s) -> no MZ", fname); | ||
| 43 | goto cleanup; | ||
| 44 | } | ||
| 45 | unsigned pe_pos = le(map, fsize, 0x3c, 4); | ||
| 46 | if (le(map, fsize, pe_pos, 4) != (('E'<<8) | 'P')) { | ||
| 47 | warnx("MsiGetFileInfo(%s) -> no PE", fname); | ||
| 48 | goto cleanup; | ||
| 49 | } | ||
| 50 | pe_pos += 4; /* skip to the main header */ | ||
| 51 | unsigned nsections = le(map, fsize, pe_pos + 2, 2); | ||
| 52 | unsigned opthdr_size = le(map, fsize, pe_pos + 16, 2); | ||
| 53 | unsigned opthdr_pos = pe_pos + 20; | ||
| 54 | /* bool sixtyfourbit = le(map, fsize, opthdr_pos, 2) == 0x020B; */ | ||
| 55 | unsigned secthdr_pos = opthdr_pos + opthdr_size; | ||
| 56 | while (nsections > 0) { | ||
| 57 | if (le(map, fsize, secthdr_pos+0, 1) == '.' && | ||
| 58 | le(map, fsize, secthdr_pos+1, 1) == 'r' && | ||
| 59 | le(map, fsize, secthdr_pos+2, 1) == 's' && | ||
| 60 | le(map, fsize, secthdr_pos+3, 1) == 'r' && | ||
| 61 | le(map, fsize, secthdr_pos+4, 1) == 'c' && | ||
| 62 | le(map, fsize, secthdr_pos+5, 1) == 0) | ||
| 63 | goto found_resource_section; | ||
| 64 | secthdr_pos += 0x28; | ||
| 65 | nsections--; | ||
| 66 | } | ||
| 67 | warnx("MsiGetFileInfo(%s) -> no .rsrc", fname); | ||
| 68 | goto cleanup; | ||
| 69 | |||
| 70 | found_resource_section:; | ||
| 71 | unsigned rsrc_size = le(map, fsize, secthdr_pos+8, 4); | ||
| 72 | unsigned rsrc_offset = le(map, fsize, secthdr_pos+20, 4); | ||
| 73 | unsigned rsrc_vaddr = le(map, fsize, secthdr_pos+12, 4); | ||
| 74 | |||
| 75 | unsigned res_dir_offset = rsrc_offset; | ||
| 76 | unsigned nnamed, nid; | ||
| 77 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 78 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 79 | for (unsigned i = nnamed; i < nnamed+nid; i++) { | ||
| 80 | unsigned id = le(map, fsize, res_dir_offset + 16 + 8*i, 4); | ||
| 81 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 82 | if (id == 16 && (entry & 0x80000000)) { | ||
| 83 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 84 | goto found_versioninfo_toplevel; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname); | ||
| 88 | goto cleanup; | ||
| 89 | |||
| 90 | found_versioninfo_toplevel: | ||
| 91 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 92 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 93 | for (unsigned i = 0; i < nnamed+nid; i++) { | ||
| 94 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 95 | if (entry & 0x80000000) { | ||
| 96 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 97 | goto found_versioninfo_2ndlevel; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname); | ||
| 101 | goto cleanup; | ||
| 102 | |||
| 103 | found_versioninfo_2ndlevel: | ||
| 104 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 105 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 106 | for (unsigned i = 0; i < nnamed+nid; i++) { | ||
| 107 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 108 | if (!(entry & 0x80000000)) { | ||
| 109 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 110 | goto found_versioninfo_3rdlevel; | ||
| 111 | } | ||
| 112 | } | ||
| 113 | warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname); | ||
| 114 | goto cleanup; | ||
| 115 | |||
| 116 | found_versioninfo_3rdlevel:; | ||
| 117 | unsigned versioninfo_offset = le(map, fsize, res_dir_offset, 4); | ||
| 118 | unsigned versioninfo_size = le(map, fsize, res_dir_offset+4, 4); | ||
| 119 | versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr; | ||
| 120 | |||
| 121 | unsigned name_offset = versioninfo_offset + 6; | ||
| 122 | const char *next_name_chr = "VS_VERSION_INFO"; | ||
| 123 | do { | ||
| 124 | if (le(map, fsize, name_offset, 2) != *next_name_chr) | ||
| 125 | goto cleanup; /* identifying string didn't match */ | ||
| 126 | name_offset += 2; | ||
| 127 | } while (*next_name_chr++); | ||
| 128 | unsigned fixed_offset = (name_offset + 3) & ~3; | ||
| 129 | if (le(map, fsize, fixed_offset, 4) != 0xFEEF04BDU) { | ||
| 130 | warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname); | ||
| 131 | goto cleanup; | ||
| 132 | } | ||
| 133 | int four_part_version[4]; | ||
| 134 | four_part_version[0] = le(map, fsize, fixed_offset + 10, 2); | ||
| 135 | four_part_version[1] = le(map, fsize, fixed_offset + 8, 2); | ||
| 136 | four_part_version[2] = le(map, fsize, fixed_offset + 14, 2); | ||
| 137 | four_part_version[3] = le(map, fsize, fixed_offset + 12, 2); | ||
| 138 | unsigned child_offset = fixed_offset + | ||
| 139 | le(map, fsize, versioninfo_offset+2, 2); | ||
| 140 | unsigned lcid; | ||
| 141 | while (child_offset < versioninfo_offset + versioninfo_size) { | ||
| 142 | unsigned this_child_offset = child_offset; | ||
| 143 | child_offset += le(map, fsize, child_offset, 2); | ||
| 144 | child_offset = (child_offset + 3) &~ 3; | ||
| 145 | if (child_offset <= this_child_offset) { | ||
| 146 | warnx("MsiGetFileInfo(%s) -> bad length field", fname); | ||
| 147 | goto cleanup; | ||
| 148 | } | ||
| 149 | const char *next_name_chr = "VarFileInfo"; | ||
| 150 | name_offset = this_child_offset + 6; | ||
| 151 | do { | ||
| 152 | if (le(map, fsize, name_offset, 2) != *next_name_chr) | ||
| 153 | goto this_is_not_a_varfileinfo; | ||
| 154 | name_offset += 2; | ||
| 155 | } while (*next_name_chr++); | ||
| 156 | unsigned subchild_offset = (name_offset + 3) & ~3; | ||
| 157 | |||
| 158 | next_name_chr = "Translation"; | ||
| 159 | name_offset = subchild_offset + 6; | ||
| 160 | do { | ||
| 161 | if (le(map, fsize, name_offset, 2) != *next_name_chr) { | ||
| 162 | warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname); | ||
| 163 | goto cleanup; | ||
| 164 | } | ||
| 165 | name_offset += 2; | ||
| 166 | } while (*next_name_chr++); | ||
| 167 | subchild_offset = (name_offset + 3) & ~3; | ||
| 168 | lcid = le(map, fsize, subchild_offset, 2); | ||
| 169 | goto success; | ||
| 170 | this_is_not_a_varfileinfo: | ||
| 171 | continue; | ||
| 172 | } | ||
| 173 | warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname); | ||
| 174 | goto cleanup; | ||
| 175 | |||
| 176 | success:; | ||
| 177 | char verbuf[256], langbuf[256]; | ||
| 178 | snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d", | ||
| 179 | four_part_version[0], four_part_version[1], | ||
| 180 | four_part_version[2], four_part_version[3]); | ||
| 181 | snprintf(langbuf, sizeof(langbuf), "%u", lcid); | ||
| 182 | warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf); | ||
| 183 | if (version) | ||
| 184 | c16cpy(version, version_size, verbuf); | ||
| 185 | if (language) | ||
| 186 | c16cpy(language, language_size, langbuf); | ||
| 187 | toret = 0; | ||
| 188 | |||
| 189 | cleanup: | ||
| 190 | if (mapv != MAP_FAILED) | ||
| 191 | munmap(mapv, fsize); | ||
| 192 | if (fd != -1) | ||
| 193 | close(fd); | ||
| 194 | sfree(fname); | ||
| 195 | return toret; | ||
| 196 | } | ||
