Tizen 2.1 base
[external/device-mapper.git] / lib / mm / memlock.c
1 /*
2  * Copyright (C) 2003-2004 Sistina Software, Inc. All rights reserved.
3  * Copyright (C) 2004-2006 Red Hat, Inc. All rights reserved.
4  *
5  * This file is part of LVM2.
6  *
7  * This copyrighted material is made available to anyone wishing to use,
8  * modify, copy, or redistribute it subject to the terms and conditions
9  * of the GNU Lesser General Public License v.2.1.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, write to the Free Software Foundation,
13  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
14  */
15
16 #include "lib.h"
17 #include "memlock.h"
18 #include "defaults.h"
19 #include "config.h"
20 #include "toolcontext.h"
21
22 #include <limits.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26 #include <sys/time.h>
27 #include <sys/resource.h>
28
29 #ifndef DEVMAPPER_SUPPORT
30
31 void memlock_inc(struct cmd_context *cmd)
32 {
33         return;
34 }
35 void memlock_dec(struct cmd_context *cmd)
36 {
37         return;
38 }
39 int memlock(void)
40 {
41         return 0;
42 }
43 void memlock_init(struct cmd_context *cmd)
44 {
45         return;
46 }
47
48 #else                           /* DEVMAPPER_SUPPORT */
49
50 static size_t _size_stack;
51 static size_t _size_malloc_tmp;
52 static size_t _size_malloc = 2000000;
53
54 static void *_malloc_mem = NULL;
55 static int _memlock_count = 0;
56 static int _memlock_count_daemon = 0;
57 static int _priority;
58 static int _default_priority;
59
60 /* list of maps, that are unconditionaly ignored */
61 static const char * const _ignore_maps[] = {
62     "[vdso]",
63     "[vsyscall]",
64 };
65
66 /* default blacklist for maps */
67 static const char * const _blacklist_maps[] = {
68     "locale/locale-archive",
69     "gconv/gconv-modules.cache",
70     "/libreadline.so.", /* not using readline during mlock */
71     "/libncurses.so.",  /* not using readline during mlock */
72     "/libdl-",          /* not using dlopen,dlsym during mlock */
73     /* "/libdevmapper-event.so" */
74 };
75
76 typedef enum { LVM_MLOCK, LVM_MUNLOCK } lvmlock_t;
77
78 static unsigned _use_mlockall;
79 static int _maps_fd;
80 static size_t _maps_len = 8192; /* Initial buffer size for reading /proc/self/maps */
81 static char *_maps_buffer;
82 static char _procselfmaps[PATH_MAX] = "";
83 #define SELF_MAPS "/self/maps"
84
85 static size_t _mstats; /* statistic for maps locking */
86
87 static void _touch_memory(void *mem, size_t size)
88 {
89         size_t pagesize = lvm_getpagesize();
90         char *pos = mem;
91         char *end = pos + size - sizeof(long);
92
93         while (pos < end) {
94                 *(long *) pos = 1;
95                 pos += pagesize;
96         }
97 }
98
99 static void _allocate_memory(void)
100 {
101         void *stack_mem, *temp_malloc_mem;
102
103         if ((stack_mem = alloca(_size_stack)))
104                 _touch_memory(stack_mem, _size_stack);
105
106         if ((temp_malloc_mem = malloc(_size_malloc_tmp)))
107                 _touch_memory(temp_malloc_mem, _size_malloc_tmp);
108
109         if ((_malloc_mem = malloc(_size_malloc)))
110                 _touch_memory(_malloc_mem, _size_malloc);
111
112         free(temp_malloc_mem);
113 }
114
115 static void _release_memory(void)
116 {
117         free(_malloc_mem);
118 }
119
120 /*
121  * mlock/munlock memory areas from /proc/self/maps
122  * format described in kernel/Documentation/filesystem/proc.txt
123  */
124 static int _maps_line(const struct config_node *cn, lvmlock_t lock,
125                       const char* line, size_t* mstats)
126 {
127         const struct config_value *cv;
128         long from, to;
129         int pos, i;
130         char fr, fw, fx, fp;
131         size_t sz;
132
133         if (sscanf(line, "%lx-%lx %c%c%c%c%n",
134                    &from, &to, &fr, &fw, &fx, &fp, &pos) != 6) {
135                 log_error("Failed to parse maps line: %s", line);
136                 return 0;
137         }
138
139         /* Select readable maps */
140         if (fr != 'r') {
141                 log_debug("%s area unreadable %s : Skipping.",
142                           (lock == LVM_MLOCK) ? "mlock" : "munlock", line);
143                 return 1;
144         }
145
146         /* always ignored areas */
147         for (i = 0; i < sizeof(_ignore_maps) / sizeof(_ignore_maps[0]); ++i)
148                 if (strstr(line + pos, _ignore_maps[i])) {
149                         log_debug("mlock ignore filter '%s' matches '%s': Skipping.",
150                                   _ignore_maps[i], line);
151                         return 1;
152                 }
153
154         sz = to - from;
155         if (!cn) {
156                 /* If no blacklist configured, use an internal set */
157                 for (i = 0; i < sizeof(_blacklist_maps) / sizeof(_blacklist_maps[0]); ++i)
158                         if (strstr(line + pos, _blacklist_maps[i])) {
159                                 log_debug("mlock default filter '%s' matches '%s': Skipping.",
160                                           _blacklist_maps[i], line);
161                                 return 1;
162                         }
163         } else {
164                 for (cv = cn->v; cv; cv = cv->next) {
165                         if ((cv->type != CFG_STRING) || !cv->v.str[0])
166                                 continue;
167                         if (strstr(line + pos, cv->v.str)) {
168                                 log_debug("mlock_filter '%s' matches '%s': Skipping.",
169                                           cv->v.str, line);
170                                 return 1;
171                         }
172                 }
173         }
174
175         *mstats += sz;
176         log_debug("%s %10ldKiB %12lx - %12lx %c%c%c%c%s",
177                   (lock == LVM_MLOCK) ? "mlock" : "munlock",
178                   ((long)sz + 1023) / 1024, from, to, fr, fw, fx, fp, line + pos);
179
180         if (lock == LVM_MLOCK) {
181                 if (mlock((const void*)from, sz) < 0) {
182                         log_sys_error("mlock", line);
183                         return 0;
184                 }
185         } else {
186                 if (munlock((const void*)from, sz) < 0) {
187                         log_sys_error("munlock", line);
188                         return 0;
189                 }
190         }
191
192         return 1;
193 }
194
195 static int _memlock_maps(struct cmd_context *cmd, lvmlock_t lock, size_t *mstats)
196 {
197         const struct config_node *cn;
198         char *line, *line_end;
199         size_t len;
200         ssize_t n;
201         int ret = 1;
202
203         if (_use_mlockall) {
204 #ifdef MCL_CURRENT
205                 if (lock == LVM_MLOCK) {
206                         if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
207                                 log_sys_error("mlockall", "");
208                                 return 0;
209                         }
210                 } else {
211                         if (munlockall()) {
212                                 log_sys_error("munlockall", "");
213                                 return 0;
214                         }
215                 }
216                 return 1;
217 #else
218                 return 0;
219 #endif
220         }
221
222         /* Force libc.mo load */
223         if (lock == LVM_MLOCK)
224                 (void)strerror(0);
225         /* Reset statistic counters */
226         *mstats = 0;
227
228         /* read mapping into a single memory chunk without reallocation
229          * in the middle of reading maps file */
230         for (len = 0;;) {
231                 if (!_maps_buffer || len >= _maps_len) {
232                         if (_maps_buffer)
233                                 _maps_len *= 2;
234                         if (!(_maps_buffer = dm_realloc(_maps_buffer, _maps_len))) {
235                                 log_error("Allocation of maps buffer failed");
236                                 return 0;
237                         }
238                 }
239                 lseek(_maps_fd, 0, SEEK_SET);
240                 for (len = 0 ; len < _maps_len; len += n) {
241                         if (!(n = read(_maps_fd, _maps_buffer + len, _maps_len - len))) {
242                                 _maps_buffer[len] = '\0';
243                                 break; /* EOF */
244                         }
245                         if (n == -1)
246                                 return_0;
247                 }
248                 if (len < _maps_len)  /* fits in buffer */
249                         break;
250         }
251
252         line = _maps_buffer;
253         cn = find_config_tree_node(cmd, "activation/mlock_filter");
254
255         while ((line_end = strchr(line, '\n'))) {
256                 *line_end = '\0'; /* remove \n */
257                 if (!_maps_line(cn, lock, line, mstats))
258                         ret = 0;
259                 line = line_end + 1;
260         }
261
262         log_debug("%socked %ld bytes",
263                   (lock == LVM_MLOCK) ? "L" : "Unl", (long)*mstats);
264
265         return ret;
266 }
267
268 /* Stop memory getting swapped out */
269 static void _lock_mem(struct cmd_context *cmd)
270 {
271         _allocate_memory();
272
273         /*
274          * For daemon we need to use mlockall()
275          * so even future adition of thread which may not even use lvm lib
276          * will not block memory locked thread
277          * Note: assuming _memlock_count_daemon is updated before _memlock_count
278          */
279         _use_mlockall = _memlock_count_daemon ? 1 :
280                 find_config_tree_bool(cmd, "activation/use_mlockall", DEFAULT_USE_MLOCKALL);
281
282         if (!_use_mlockall) {
283                 if (!*_procselfmaps &&
284                     dm_snprintf(_procselfmaps, sizeof(_procselfmaps),
285                                 "%s" SELF_MAPS, cmd->proc_dir) < 0) {
286                         log_error("proc_dir too long");
287                         return;
288                 }
289
290                 if (!(_maps_fd = open(_procselfmaps, O_RDONLY))) {
291                         log_sys_error("open", _procselfmaps);
292                         return;
293                 }
294         }
295
296         log_very_verbose("Locking memory");
297         if (!_memlock_maps(cmd, LVM_MLOCK, &_mstats))
298                 stack;
299
300         errno = 0;
301         if (((_priority = getpriority(PRIO_PROCESS, 0)) == -1) && errno)
302                 log_sys_error("getpriority", "");
303         else
304                 if (setpriority(PRIO_PROCESS, 0, _default_priority))
305                         log_error("setpriority %d failed: %s",
306                                   _default_priority, strerror(errno));
307 }
308
309 static void _unlock_mem(struct cmd_context *cmd)
310 {
311         size_t unlock_mstats;
312
313         log_very_verbose("Unlocking memory");
314
315         if (!_memlock_maps(cmd, LVM_MUNLOCK, &unlock_mstats))
316                 stack;
317
318         if (!_use_mlockall) {
319                 if (close(_maps_fd))
320                         log_sys_error("close", _procselfmaps);
321                 dm_free(_maps_buffer);
322                 _maps_buffer = NULL;
323                 if (_mstats < unlock_mstats)
324                         log_error(INTERNAL_ERROR "Maps lock %ld < unlock %ld",
325                                   (long)_mstats, (long)unlock_mstats);
326         }
327
328         if (setpriority(PRIO_PROCESS, 0, _priority))
329                 log_error("setpriority %u failed: %s", _priority,
330                           strerror(errno));
331         _release_memory();
332 }
333
334 static void _lock_mem_if_needed(struct cmd_context *cmd)
335 {
336         if ((_memlock_count + _memlock_count_daemon) == 1)
337                 _lock_mem(cmd);
338 }
339
340 static void _unlock_mem_if_possible(struct cmd_context *cmd)
341 {
342         if ((_memlock_count + _memlock_count_daemon) == 0)
343                 _unlock_mem(cmd);
344 }
345
346 void memlock_inc(struct cmd_context *cmd)
347 {
348         ++_memlock_count;
349         _lock_mem_if_needed(cmd);
350         log_debug("memlock_count inc to %d", _memlock_count);
351 }
352
353 void memlock_dec(struct cmd_context *cmd)
354 {
355         if (!_memlock_count)
356                 log_error(INTERNAL_ERROR "_memlock_count has dropped below 0.");
357         --_memlock_count;
358         _unlock_mem_if_possible(cmd);
359         log_debug("memlock_count dec to %d", _memlock_count);
360 }
361
362 /*
363  * The memlock_*_daemon functions will force the mlockall() call that we need
364  * to stay in memory, but they will have no effect on device scans (unlike
365  * normal memlock_inc and memlock_dec). Memory is kept locked as long as either
366  * of memlock or memlock_daemon is in effect.
367  */
368
369 void memlock_inc_daemon(struct cmd_context *cmd)
370 {
371         ++_memlock_count_daemon;
372         if (_memlock_count_daemon == 1 && _memlock_count > 0)
373                 log_error(INTERNAL_ERROR "_memlock_inc_daemon used after _memlock_inc.");
374         _lock_mem_if_needed(cmd);
375         log_debug("memlock_count_daemon inc to %d", _memlock_count_daemon);
376 }
377
378 void memlock_dec_daemon(struct cmd_context *cmd)
379 {
380         if (!_memlock_count_daemon)
381                 log_error(INTERNAL_ERROR "_memlock_count_daemon has dropped below 0.");
382         --_memlock_count_daemon;
383         _unlock_mem_if_possible(cmd);
384         log_debug("memlock_count_daemon dec to %d", _memlock_count_daemon);
385 }
386
387 /*
388  * This disregards the daemon (dmeventd) locks, since we use memlock() to check
389  * whether it is safe to run a device scan, which would normally coincide with
390  * !memlock() -- but the daemon global memory lock breaks this assumption, so
391  * we do not take those into account here.
392  */
393 int memlock(void)
394 {
395         return _memlock_count;
396 }
397
398 void memlock_init(struct cmd_context *cmd)
399 {
400         _size_stack = find_config_tree_int(cmd,
401                                       "activation/reserved_stack",
402                                       DEFAULT_RESERVED_STACK) * 1024;
403         _size_malloc_tmp = find_config_tree_int(cmd,
404                                            "activation/reserved_memory",
405                                            DEFAULT_RESERVED_MEMORY) * 1024;
406         _default_priority = find_config_tree_int(cmd,
407                                             "activation/process_priority",
408                                             DEFAULT_PROCESS_PRIORITY);
409 }
410
411 #endif