chiark / gitweb /
implement a union to pad out file_handle
[elogind.git] / src / readahead / readahead-common.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/sysinfo.h>
26 #include <sys/inotify.h>
27 #include <fcntl.h>
28 #include <sys/mman.h>
29 #include <unistd.h>
30
31 #include "log.h"
32 #include "readahead-common.h"
33 #include "util.h"
34 #include "missing.h"
35 #include "fileio.h"
36 #include "libudev.h"
37 #include "udev-util.h"
38
39 int file_verify(int fd, const char *fn, off_t file_size_max, struct stat *st) {
40         assert(fd >= 0);
41         assert(fn);
42         assert(st);
43
44         if (fstat(fd, st) < 0) {
45                 log_warning("fstat(%s) failed: %m", fn);
46                 return -errno;
47         }
48
49         if (!S_ISREG(st->st_mode)) {
50                 log_debug("Not preloading special file %s", fn);
51                 return 0;
52         }
53
54         if (st->st_size <= 0 || st->st_size > file_size_max) {
55                 log_debug("Not preloading file %s with size out of bounds %llu", fn, (unsigned long long) st->st_size);
56                 return 0;
57         }
58
59         return 1;
60 }
61
62 int fs_on_ssd(const char *p) {
63         struct stat st;
64         _cleanup_udev_unref_ struct udev *udev = NULL;
65         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
66         struct udev_device *look_at = NULL;
67         const char *devtype, *rotational, *model, *id;
68         int r;
69
70         assert(p);
71
72         if (stat(p, &st) < 0)
73                 return -errno;
74
75         if (major(st.st_dev) == 0) {
76                 _cleanup_fclose_ FILE *f = NULL;
77                 int mount_id;
78                 union file_handle_union h = { .handle.handle_bytes = MAX_HANDLE_SZ, };
79
80                 /* Might be btrfs, which exposes "ssd" as mount flag if it is on ssd.
81                  *
82                  * We first determine the mount ID here, if we can,
83                  * and then lookup the mount ID in mountinfo to find
84                  * the mount options. */
85
86                 r = name_to_handle_at(AT_FDCWD, p, &h.handle, &mount_id, AT_SYMLINK_FOLLOW);
87                 if (r < 0)
88                         return false;
89
90                 f = fopen("/proc/self/mountinfo", "re");
91                 if (!f)
92                         return false;
93
94                 for (;;) {
95                         char line[LINE_MAX], *e;
96                         _cleanup_free_ char *opts = NULL;
97                         int mid;
98
99                         if (!fgets(line, sizeof(line), f))
100                                 return false;
101
102                         truncate_nl(line);
103
104                         if (sscanf(line, "%i", &mid) != 1)
105                                 continue;
106
107                         if (mid != mount_id)
108                                 continue;
109
110                         e = strstr(line, " - ");
111                         if (!e)
112                                 continue;
113
114                         if (sscanf(e+3, "%*s %*s %ms", &opts) != 1)
115                                 continue;
116
117                         if (streq(opts, "ssd") || startswith(opts, "ssd,") || endswith(opts, ",ssd") || strstr(opts, ",ssd,"))
118                                 return true;
119                 }
120
121                 return false;
122         }
123
124         udev = udev_new();
125         if (!udev)
126                 return -ENOMEM;
127
128         udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
129         if (!udev_device)
130                 return false;
131
132         devtype = udev_device_get_property_value(udev_device, "DEVTYPE");
133         if (devtype && streq(devtype, "partition"))
134                 look_at = udev_device_get_parent(udev_device);
135         else
136                 look_at = udev_device;
137
138         if (!look_at)
139                 return false;
140
141         /* First, try high-level property */
142         id = udev_device_get_property_value(look_at, "ID_SSD");
143         if (id)
144                 return streq(id, "1");
145
146         /* Second, try kernel attribute */
147         rotational = udev_device_get_sysattr_value(look_at, "queue/rotational");
148         if (rotational)
149                 return streq(rotational, "0");
150
151         /* Finally, fallback to heuristics */
152         look_at = udev_device_get_parent(look_at);
153         if (!look_at)
154                 return false;
155
156         model = udev_device_get_sysattr_value(look_at, "model");
157         if (model)
158                 return !!strstr(model, "SSD");
159
160         return false;
161 }
162
163 int fs_on_read_only(const char *p) {
164         struct stat st;
165         _cleanup_udev_unref_ struct udev *udev = NULL;
166         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
167         const char *read_only;
168
169         assert(p);
170
171         if (stat(p, &st) < 0)
172                 return -errno;
173
174         if (major(st.st_dev) == 0)
175                 return false;
176
177         udev = udev_new();
178         if (!udev)
179                 return -ENOMEM;
180
181         udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
182         if (!udev_device)
183                 return false;
184
185         read_only = udev_device_get_sysattr_value(udev_device, "ro");
186         if (read_only)
187                 return streq(read_only, "1");
188
189         return false;
190 }
191
192 bool enough_ram(void) {
193         struct sysinfo si;
194
195         assert_se(sysinfo(&si) >= 0);
196
197         /* Enable readahead only with at least 128MB memory */
198         return si.totalram > 127 * 1024*1024 / si.mem_unit;
199 }
200
201 static void mkdirs(void) {
202         if (mkdir("/run/systemd", 0755) && errno != EEXIST)
203                 log_warning("Failed to create /run/systemd: %m");
204         if (mkdir("/run/systemd/readahead", 0755) && errno != EEXIST)
205                 log_warning("Failed to create /run/systemd: %m");
206 }
207
208 int open_inotify(void) {
209         int fd;
210
211         fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
212         if (fd < 0) {
213                 log_error("Failed to create inotify handle: %m");
214                 return -errno;
215         }
216
217         mkdirs();
218
219         if (inotify_add_watch(fd, "/run/systemd/readahead", IN_CREATE) < 0) {
220                 log_error("Failed to watch /run/systemd/readahead: %m");
221                 safe_close(fd);
222                 return -errno;
223         }
224
225         return fd;
226 }
227
228 ReadaheadShared *shared_get(void) {
229         int _cleanup_close_ fd = -1;
230         ReadaheadShared *m = NULL;
231
232         mkdirs();
233
234         fd = open("/run/systemd/readahead/shared", O_CREAT|O_RDWR|O_CLOEXEC, 0644);
235         if (fd < 0) {
236                 log_error("Failed to create shared memory segment: %m");
237                 return NULL;
238         }
239
240         if (ftruncate(fd, sizeof(ReadaheadShared)) < 0) {
241                 log_error("Failed to truncate shared memory segment: %m");
242                 return NULL;
243         }
244
245         m = mmap(NULL, sizeof(ReadaheadShared), PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
246         if (m == MAP_FAILED) {
247                 log_error("Failed to mmap shared memory segment: %m");
248                 return NULL;
249         }
250
251         return m;
252 }
253
254 /* We use 20K instead of the more human digestable 16K here. Why?
255    Simply so that it is more unlikely that users end up picking this
256    value too so that we can recognize better whether the user changed
257    the value while we had it temporarily bumped. */
258 #define BUMP_REQUEST_NR (20*1024)
259
260 int block_bump_request_nr(const char *p) {
261         struct stat st;
262         uint64_t u;
263         char *ap = NULL, *line = NULL;
264         int r;
265         dev_t d;
266
267         assert(p);
268
269         if (stat(p, &st) < 0)
270                 return -errno;
271
272         if (major(st.st_dev) == 0)
273                 return 0;
274
275         d = st.st_dev;
276         block_get_whole_disk(d, &d);
277
278         if (asprintf(&ap, "/sys/dev/block/%u:%u/queue/nr_requests", major(d), minor(d)) < 0) {
279                 r= -ENOMEM;
280                 goto finish;
281         }
282
283         r = read_one_line_file(ap, &line);
284         if (r < 0) {
285                 if (r == -ENOENT)
286                         r = 0;
287                 goto finish;
288         }
289
290         r = safe_atou64(line, &u);
291         if (r >= 0 && u >= BUMP_REQUEST_NR) {
292                 r = 0;
293                 goto finish;
294         }
295
296         free(line);
297         line = NULL;
298
299         if (asprintf(&line, "%lu", (unsigned long) BUMP_REQUEST_NR) < 0) {
300                 r = -ENOMEM;
301                 goto finish;
302         }
303
304         r = write_string_file(ap, line);
305         if (r < 0)
306                 goto finish;
307
308         log_info("Bumped block_nr parameter of %u:%u to %lu. This is a temporary hack and should be removed one day.", major(d), minor(d), (unsigned long) BUMP_REQUEST_NR);
309         r = 1;
310
311 finish:
312         free(ap);
313         free(line);
314
315         return r;
316 }
317
318 int block_get_readahead(const char *p, uint64_t *bytes) {
319         struct stat st;
320         char *ap = NULL, *line = NULL;
321         int r;
322         dev_t d;
323         uint64_t u;
324
325         assert(p);
326         assert(bytes);
327
328         if (stat(p, &st) < 0)
329                 return -errno;
330
331         if (major(st.st_dev) == 0)
332                 return 0;
333
334         d = st.st_dev;
335         block_get_whole_disk(d, &d);
336
337         if (asprintf(&ap, "/sys/dev/block/%u:%u/bdi/read_ahead_kb", major(d), minor(d)) < 0) {
338                 r = -ENOMEM;
339                 goto finish;
340         }
341
342         r = read_one_line_file(ap, &line);
343         if (r < 0)
344                 goto finish;
345
346         r = safe_atou64(line, &u);
347         if (r < 0)
348                 goto finish;
349
350         *bytes = u * 1024ULL;
351
352 finish:
353         free(ap);
354         free(line);
355
356         return r;
357 }
358
359 int block_set_readahead(const char *p, uint64_t bytes) {
360         struct stat st;
361         char *ap = NULL, *line = NULL;
362         int r;
363         dev_t d;
364
365         assert(p);
366         assert(bytes);
367
368         if (stat(p, &st) < 0)
369                 return -errno;
370
371         if (major(st.st_dev) == 0)
372                 return 0;
373
374         d = st.st_dev;
375         block_get_whole_disk(d, &d);
376
377         if (asprintf(&ap, "/sys/dev/block/%u:%u/bdi/read_ahead_kb", major(d), minor(d)) < 0) {
378                 r = -ENOMEM;
379                 goto finish;
380         }
381
382         if (asprintf(&line, "%llu", (unsigned long long) bytes / 1024ULL) < 0) {
383                 r = -ENOMEM;
384                 goto finish;
385         }
386
387         r = write_string_file(ap, line);
388         if (r < 0)
389                 goto finish;
390
391 finish:
392         free(ap);
393         free(line);
394
395         return r;
396 }