1 /* Copyright (c) 2003-2004, Roger Dingledine
2  * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3  * Copyright (c) 2007-2021, The Tor Project, Inc. */
4 /* See LICENSE for licensing information */
5 
6 /**
7  * \file mmap.c
8  *
9  * \brief Cross-platform support for mapping files into our address space.
10  **/
11 
12 #include "lib/fs/mmap.h"
13 #include "lib/fs/files.h"
14 #include "lib/log/log.h"
15 #include "lib/log/util_bug.h"
16 #include "lib/log/win32err.h"
17 #include "lib/string/compat_string.h"
18 #include "lib/malloc/malloc.h"
19 
20 #ifdef HAVE_MMAP
21 #include <sys/mman.h>
22 #endif
23 #ifdef HAVE_SYS_STAT_H
24 #include <sys/stat.h>
25 #endif
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29 #ifdef HAVE_FCNTL_H
30 #include <fcntl.h>
31 #endif
32 
33 #ifdef _WIN32
34 #include <windows.h>
35 #endif
36 
37 #include <errno.h>
38 #include <string.h>
39 
40 #if defined(HAVE_MMAP) || defined(RUNNING_DOXYGEN)
41 /** Try to create a memory mapping for <b>filename</b> and return it.  On
42  * failure, return NULL. Sets errno properly, using ERANGE to mean
43  * "empty file". Must only be called on trusted Tor-owned files, as changing
44  * the underlying file's size causes unspecified behavior. */
45 MOCK_IMPL(tor_mmap_t *,
46 tor_mmap_file,(const char *filename))
47 {
48   int fd; /* router file */
49   char *string;
50   int result;
51   tor_mmap_t *res;
52   size_t size, filesize;
53   struct stat st;
54 
55   tor_assert(filename);
56 
57   fd = tor_open_cloexec(filename, O_RDONLY, 0);
58   if (fd<0) {
59     int save_errno = errno;
60     int severity = (errno == ENOENT) ? LOG_INFO : LOG_WARN;
61     log_fn(severity, LD_FS,"Could not open \"%s\" for mmap(): %s",filename,
62            strerror(errno));
63     errno = save_errno;
64     return NULL;
65   }
66 
67   /* Get the size of the file */
68   result = fstat(fd, &st);
69   if (result != 0) {
70     int save_errno = errno;
71     log_warn(LD_FS,
72              "Couldn't fstat opened descriptor for \"%s\" during mmap: %s",
73              filename, strerror(errno));
74     close(fd);
75     errno = save_errno;
76     return NULL;
77   }
78   size = filesize = (size_t)(st.st_size);
79 
80   if (st.st_size > SSIZE_T_CEILING || (off_t)size < st.st_size) {
81     log_warn(LD_FS, "File \"%s\" is too large. Ignoring.",filename);
82     errno = EFBIG;
83     close(fd);
84     return NULL;
85   }
86   if (!size) {
87     /* Zero-length file. If we call mmap on it, it will succeed but
88      * return NULL, and bad things will happen. So just fail. */
89     log_info(LD_FS,"File \"%s\" is empty. Ignoring.",filename);
90     errno = ERANGE;
91     close(fd);
92     return NULL;
93   }
94 
95   string = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
96   close(fd);
97   if (string == MAP_FAILED) {
98     int save_errno = errno;
99     log_warn(LD_FS,"Could not mmap file \"%s\": %s", filename,
100              strerror(errno));
101     errno = save_errno;
102     return NULL;
103   }
104 
105   res = tor_malloc_zero(sizeof(tor_mmap_t));
106   res->data = string;
107   res->size = filesize;
108   res->mapping_size = size;
109 
110   return res;
111 }
112 /** Release storage held for a memory mapping; returns 0 on success,
113  * or -1 on failure (and logs a warning). */
114 MOCK_IMPL(int,
115 tor_munmap_file,(tor_mmap_t *handle))
116 {
117   int res;
118 
119   if (handle == NULL)
120     return 0;
121 
122   res = munmap((char*)handle->data, handle->mapping_size);
123   if (res == 0) {
124     /* munmap() succeeded */
125     tor_free(handle);
126   } else {
127     log_warn(LD_FS, "Failed to munmap() in tor_munmap_file(): %s",
128              strerror(errno));
129     res = -1;
130   }
131 
132   return res;
133 }
134 #elif defined(_WIN32)
135 MOCK_IMPL(tor_mmap_t *,
136 tor_mmap_file,(const char *filename))
137 {
138   TCHAR tfilename[MAX_PATH]= {0};
139   tor_mmap_t *res = tor_malloc_zero(sizeof(tor_mmap_t));
140   int empty = 0;
141   HANDLE file_handle = INVALID_HANDLE_VALUE;
142   DWORD size_low, size_high;
143   uint64_t real_size;
144   res->mmap_handle = NULL;
145 #ifdef UNICODE
146   mbstowcs(tfilename,filename,MAX_PATH);
147 #else
148   strlcpy(tfilename,filename,MAX_PATH);
149 #endif
150   file_handle = CreateFile(tfilename,
151                            GENERIC_READ, FILE_SHARE_READ,
152                            NULL,
153                            OPEN_EXISTING,
154                            FILE_ATTRIBUTE_NORMAL,
155                            0);
156 
157   if (file_handle == INVALID_HANDLE_VALUE)
158     goto win_err;
159 
160   size_low = GetFileSize(file_handle, &size_high);
161 
162   if (size_low == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) {
163     log_warn(LD_FS,"Error getting size of \"%s\".",filename);
164     goto win_err;
165   }
166   if (size_low == 0 && size_high == 0) {
167     log_info(LD_FS,"File \"%s\" is empty. Ignoring.",filename);
168     empty = 1;
169     goto err;
170   }
171   real_size = (((uint64_t)size_high)<<32) | size_low;
172   if (real_size > SIZE_MAX) {
173     log_warn(LD_FS,"File \"%s\" is too big to map; not trying.",filename);
174     goto err;
175   }
176   res->size = real_size;
177 
178   res->mmap_handle = CreateFileMapping(file_handle,
179                                        NULL,
180                                        PAGE_READONLY,
181                                        size_high,
182                                        size_low,
183                                        NULL);
184   if (res->mmap_handle == NULL)
185     goto win_err;
186   res->data = (char*) MapViewOfFile(res->mmap_handle,
187                                     FILE_MAP_READ,
188                                     0, 0, 0);
189   if (!res->data)
190     goto win_err;
191 
192   CloseHandle(file_handle);
193   return res;
194  win_err: {
195     DWORD e = GetLastError();
196     int severity = (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) ?
197       LOG_INFO : LOG_WARN;
198     char *msg = format_win32_error(e);
199     log_fn(severity, LD_FS, "Couldn't mmap file \"%s\": %s", filename, msg);
200     tor_free(msg);
201     if (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND)
202       errno = ENOENT;
203     else
204       errno = EINVAL;
205   }
206  err:
207   if (empty)
208     errno = ERANGE;
209   if (file_handle != INVALID_HANDLE_VALUE)
210     CloseHandle(file_handle);
211   tor_munmap_file(res);
212   return NULL;
213 }
214 
215 /* Unmap the file, and return 0 for success or -1 for failure */
216 MOCK_IMPL(int,
217 tor_munmap_file,(tor_mmap_t *handle))
218 {
219   if (handle == NULL)
220     return 0;
221 
222   if (handle->data) {
223     /* This is an ugly cast, but without it, "data" in struct tor_mmap_t would
224        have to be redefined as non-const. */
225     BOOL ok = UnmapViewOfFile( (LPVOID) handle->data);
226     if (!ok) {
227       log_warn(LD_FS, "Failed to UnmapViewOfFile() in tor_munmap_file(): %d",
228                (int)GetLastError());
229     }
230   }
231 
232   if (handle->mmap_handle != NULL)
233     CloseHandle(handle->mmap_handle);
234   tor_free(handle);
235 
236   return 0;
237 }
238 #else
239 #error "cannot implement tor_mmap_file"
240 #endif /* defined(HAVE_MMAP) || defined(RUNNING_DOXYGEN) || ... */
241