1 /*
2  * This code is (c) 2012 Johannes Thoma
3  *
4  * modified 2018 for tmpfile by D. Mitch Bailey
5  *
6  * This file is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This file is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this file.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * You can download original source from https://github.com/johannesthoma/mmap_allocator.git
20  */
21 
22 #include "mmap_file_pool.h"
23 #include "mmap_exception.h"
24 #include <stdio.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <sys/mman.h>
29 #include <unistd.h>
30 #include <assert.h>
31 
32 namespace mmap_allocator_namespace {
33 
34 #ifndef MMAP_ALLOCATOR_DEBUG
35 #define MMAP_ALLOCATOR_DEBUG 0
36 #endif
37 
38 	int verbosity = MMAP_ALLOCATOR_DEBUG;
39 
set_verbosity(int v)40 	void set_verbosity(int v)
41 	{
42 		verbosity = v;
43 	}
44 
get_verbosity(void)45 	int get_verbosity(void)
46 	{
47 		return verbosity;
48 	}
49 
filesize(int fd,std::string fname)50 	off_t filesize(int fd, std::string fname)
51 	{
52 		struct stat buf;
53 		if (fstat(fd, &buf) < 0) {
54 			if (get_verbosity() > 0) {
55 				perror("stat");
56 			}
57 
58 			throw mmap_allocator_exception("Cannot stat file" + fname);
59 		}
60 
61 		return buf.st_size;
62 	}
63 
mmap_file_identifier(std::string fname,enum access_mode access_mode_param)64 	mmap_file_identifier::mmap_file_identifier(std::string fname, enum access_mode access_mode_param)
65 	{
66 		struct stat buf;
67 		if (stat(fname.c_str(), &buf) < 0) {
68 			if (get_verbosity() > 0) {
69 				perror("stat");
70 			}
71 			throw mmap_allocator_exception("Cannot stat file "+fname);
72 		}
73 
74 		device = buf.st_dev;
75 		inode = buf.st_ino;
76 		access_mode = access_mode_param;
77 	}
78 
mmap_file_identifier(const mmap_file_identifier & other)79 	mmap_file_identifier::mmap_file_identifier(const mmap_file_identifier &other)
80 	{
81 		device = other.device;
82 		inode = other.inode;
83 		access_mode = other.access_mode;
84 	}
85 
operator ==(const mmap_file_identifier & other) const86 	bool mmap_file_identifier::operator==(const mmap_file_identifier &other) const
87 	{
88 		return device == other.device &&
89 			inode == other.inode &&
90 			access_mode == other.access_mode;
91 	}
92 
operator <(const mmap_file_identifier & other) const93 	bool mmap_file_identifier::operator<(const mmap_file_identifier &other) const
94 	{
95 		return inode < other.inode ||
96 			(inode == other.inode && access_mode < other.access_mode) ||
97 			(inode == other.inode && access_mode == other.access_mode && device < other.device);
98 
99 	}
100 
remmap_file_for_read()101 	void mmapped_file::remmap_file_for_read()
102 	{
103 		if ( msync(memory_area, size_mapped, MS_SYNC) < 0 ) {
104 			if (get_verbosity() > 0) {
105 				perror("msync");
106 			}
107 			throw mmap_allocator_exception("Error in remmap(msync)");
108 		}
109 		if ( munmap(memory_area, size_mapped) < 0) {
110 			if (get_verbosity() > 0) {
111 				perror("munmap");
112 			}
113 			throw mmap_allocator_exception("Error in remmap(munmap)");
114 		}
115 		if ( fd == -1 )
116 			throw mmap_allocator_exception("Error in remmap(fd)");
117 
118 		void *last_address = memory_area;
119 #if defined(__FreeBSD__) || defined(__DragonFly__)
120 		memory_area = mmap(last_address, size_mapped, PROT_READ, MAP_SHARED, fd, 0);
121 #else
122 		memory_area = mmap(last_address, size_mapped, PROT_READ, MAP_SHARED | MAP_NORESERVE, fd, 0);
123 #endif
124 
125 		if (memory_area == MAP_FAILED) {
126 			if (get_verbosity() > 0) {
127 				perror("mmap");
128 			}
129 			throw mmap_allocator_exception("Error in remmap(mmap)");
130 		}
131 		if ( memory_area != last_address ) {
132 			throw mmap_allocator_exception("Error in remmap(moved)");
133 		}
134 		madvise(memory_area, size_mapped, MADV_RANDOM | MADV_DONTNEED);
135 	}
136 
open_and_mmap_file(std::string filename,enum access_mode access_mode,off_t offset,size_t length,bool map_whole_file,bool allow_remap)137 	void *mmapped_file::open_and_mmap_file(std::string filename, enum access_mode access_mode, off_t offset, size_t length, bool map_whole_file, bool allow_remap)
138 	{
139 		/* Unspecified filename opens temporary file.
140 		if (filename.c_str()[0] == '\0') {
141 			throw mmap_allocator_exception("mmap_allocator not correctly initialized: filename is empty.");
142 		}
143 		*/
144 		int mode;
145 		int prot;
146 		int mmap_mode = 0;
147 		off_t offset_to_map;
148 		size_t length_to_map;
149 		void *address_to_map = NULL;
150 
151 		if (memory_area != NULL) {
152 			address_to_map = memory_area;
153 
154 	/* do not use MAP_FIXED, since that may invalidate other memory
155 	   areas in the process, such as shared libraries, which would
156 	   lead to a mystic Segfault. */
157 
158 		}
159 		switch (access_mode) {
160 		case READ_ONLY: mode = O_RDONLY; prot = PROT_READ; mmap_mode |= MAP_SHARED; break;
161 		case READ_WRITE_SHARED: mode = O_RDWR; prot = PROT_READ | PROT_WRITE; mmap_mode |= MAP_SHARED; break;
162 		case READ_WRITE_PRIVATE: mode = O_RDONLY; prot = PROT_READ | PROT_WRITE; mmap_mode |= MAP_PRIVATE; break;
163 		default: throw mmap_allocator_exception("Internal error"); break;
164 		}
165 
166 		FILE * myTemporaryFile;
167 		if (fd == -1) {
168 			if ( filename.empty() ) {
169 				myTemporaryFile = tmpfile();
170 				fd = fileno(myTemporaryFile);
171 				ftruncate(fd, length);  // can not mmap empty file, so create it
172 			} else {
173 				fd = open(filename.c_str(), mode);
174 			}
175 			if (fd < 0) {
176 				if (get_verbosity() > 0) {
177 					perror("open");
178 				}
179 
180 				throw mmap_allocator_exception("Error opening file " + filename);
181 			}
182 		}
183 		if (map_whole_file) {
184 			offset_to_map = 0;
185 			length_to_map = ( filename.empty() ) ? length : filesize(fd, filename);
186 		} else {
187 			offset_to_map = ALIGN_TO_PAGE(offset);
188 			length_to_map = UPPER_ALIGN_TO_PAGE(length);
189 		}
190 
191 		if (offset_to_map == offset_mapped && length_to_map == size_mapped) {
192 			reference_count++;
193 			return ((char*)memory_area)+offset-offset_mapped;
194 		}
195 		if (offset_to_map >= offset_mapped && length_to_map + offset_to_map - offset_mapped <= size_mapped)
196 		{
197 			reference_count++;
198 			return ((char*)memory_area)+offset-offset_mapped;
199 		}
200 
201 		if (memory_area != NULL) {
202 			if (munmap(memory_area, size_mapped) < 0) {
203 				if (get_verbosity() > 0) {
204 					perror("munmap");
205 				}
206 				throw mmap_allocator_exception("Error in munmap file "+filename);
207 			}
208 		}
209 		memory_area = mmap(address_to_map, length_to_map, prot, mmap_mode, fd, offset_to_map);
210 		if (address_to_map != NULL && !allow_remap && memory_area != MAP_FAILED && memory_area != address_to_map) {
211 			if (munmap(memory_area, length_to_map) < 0) {
212 				if (get_verbosity() > 0) {
213 					perror("munmap");
214 				}
215 				throw mmap_allocator_exception("Error in munmap" + filename);
216 			}
217 			throw mmap_allocator_exception("Request to remap area but allow_remap is not given (remapping "+filename+")");
218 		}
219 
220 		if (memory_area == MAP_FAILED) {
221 			if (get_verbosity() > 0) {
222 				perror("mmap");
223 			}
224 			throw mmap_allocator_exception("Error in mmap "+filename);
225 		}
226 		offset_mapped = offset_to_map;
227 		size_mapped = length_to_map;
228 		reference_count++;
229 
230 		void *ret = ((char*)memory_area)+offset-offset_to_map;
231 		// assert(ret >= memory_area && ret < (char*)memory_area+size_mapped);
232 
233 		return ret;
234 	}
235 
munmap_and_close_file(void)236 	bool mmapped_file::munmap_and_close_file(void)
237 	{
238 		reference_count--;
239 		if (reference_count > 0) {
240 			return false;
241 		}
242 		if (munmap(memory_area, size_mapped) < 0) {
243 			if (get_verbosity() > 0) {
244 				perror("munmap");
245 			}
246 			throw mmap_allocator_exception("Error in munmap");
247 		}
248 		if (close(fd)) {
249 			if (get_verbosity() > 0) {
250 				perror("close");
251 			}
252 			throw mmap_allocator_exception("Error in close");
253 		}
254 		fd = -1;
255 		return true;
256 	}
257 
mmap_file(std::string fname,enum access_mode access_mode,off_t offset,size_t length,bool map_whole_file,bool allow_remap)258 	void *mmap_file_pool::mmap_file(std::string fname, enum access_mode access_mode, off_t offset, size_t length, bool map_whole_file, bool allow_remap)
259 	{
260 		mmap_file_identifier the_identifier(fname, access_mode);
261 		mmapped_file_map_t::iterator it;
262 
263 		it = the_map.find(the_identifier);
264 		if (it != the_map.end()) {
265 			return it->second.open_and_mmap_file(fname, access_mode, offset, length, map_whole_file, allow_remap);
266 		} else {
267 			mmapped_file the_file;
268 			void *ret;
269 
270 			ret = the_file.open_and_mmap_file(fname, access_mode, offset, length, map_whole_file, allow_remap);
271 			the_map.insert(mmapped_file_pair_t(the_identifier, the_file));
272 			return ret;
273 		}
274 	}
275 
munmap_file(std::string fname,enum access_mode access_mode,off_t offset,size_t length)276 	void mmap_file_pool::munmap_file(std::string fname, enum access_mode access_mode, off_t offset, size_t length)
277 	{
278 		mmap_file_identifier the_identifier(fname, access_mode);
279 		mmapped_file_map_t::iterator it;
280 
281 		it = the_map.find(the_identifier);
282 		if (it != the_map.end()) {
283 			if (it->second.munmap_and_close_file()) {
284 				the_map.erase(it);
285 			}
286 		} else {
287 			throw mmap_allocator_exception("File "+fname+" not found in pool");
288 		}
289 	}
290 
291 	mmap_file_pool the_pool; /* TODO: move to app object */
292 }
293