chiark / gitweb /
resolve: update sd-resolve to match the other APIs in style and functionality
[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                 struct file_handle *h;
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                 h = alloca(MAX_HANDLE_SZ);
87                 h->handle_bytes = MAX_HANDLE_SZ;
88                 r = name_to_handle_at(AT_FDCWD, p, h, &mount_id, AT_SYMLINK_FOLLOW);
89                 if (r < 0)
90                         return false;
91
92                 f = fopen("/proc/self/mountinfo", "re");
93                 if (!f)
94                         return false;
95
96                 for (;;) {
97                         char line[LINE_MAX], *e;
98                         _cleanup_free_ char *opts = NULL;
99                         int mid;
100
101                         if (!fgets(line, sizeof(line), f))
102                                 return false;
103
104                         truncate_nl(line);
105
106                         if (sscanf(line, "%i", &mid) != 1)
107                                 continue;
108
109                         if (mid != mount_id)
110                                 continue;
111
112                         e = strstr(line, " - ");
113                         if (!e)
114                                 continue;
115
116                         if (sscanf(e+3, "%*s %*s %ms", &opts) != 1)
117                                 continue;
118
119                         if (streq(opts, "ssd") || startswith(opts, "ssd,") || endswith(opts, ",ssd") || strstr(opts, ",ssd,"))
120                                 return true;
121                 }
122
123                 return false;
124         }
125
126         udev = udev_new();
127         if (!udev)
128                 return -ENOMEM;
129
130         udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
131         if (!udev_device)
132                 return false;
133
134         devtype = udev_device_get_property_value(udev_device, "DEVTYPE");
135         if (devtype && streq(devtype, "partition"))
136                 look_at = udev_device_get_parent(udev_device);
137         else
138                 look_at = udev_device;
139
140         if (!look_at)
141                 return false;
142
143         /* First, try high-level property */
144         id = udev_device_get_property_value(look_at, "ID_SSD");
145         if (id)
146                 return streq(id, "1");
147
148         /* Second, try kernel attribute */
149         rotational = udev_device_get_sysattr_value(look_at, "queue/rotational");
150         if (rotational)
151                 return streq(rotational, "0");
152
153         /* Finally, fallback to heuristics */
154         look_at = udev_device_get_parent(look_at);
155         if (!look_at)
156                 return false;
157
158         model = udev_device_get_sysattr_value(look_at, "model");
159         if (model)
160                 return !!strstr(model, "SSD");
161
162         return false;
163 }
164
165 int fs_on_read_only(const char *p) {
166         struct stat st;
167         _cleanup_udev_unref_ struct udev *udev = NULL;
168         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
169         const char *read_only;
170
171         assert(p);
172
173         if (stat(p, &st) < 0)
174                 return -errno;
175
176         if (major(st.st_dev) == 0)
177                 return false;
178
179         udev = udev_new();
180         if (!udev)
181                 return -ENOMEM;
182
183         udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
184         if (!udev_device)
185                 return false;
186
187         read_only = udev_device_get_sysattr_value(udev_device, "ro");
188         if (read_only)
189                 return streq(read_only, "1");
190
191         return false;
192 }
193
194 bool enough_ram(void) {
195         struct sysinfo si;
196
197         assert_se(sysinfo(&si) >= 0);
198
199         /* Enable readahead only with at least 128MB memory */
200         return si.totalram > 127 * 1024*1024 / si.mem_unit;
201 }
202
203 static void mkdirs(void) {
204         if (mkdir("/run/systemd", 0755) && errno != EEXIST)
205                 log_warning("Failed to create /run/systemd: %m");
206         if (mkdir("/run/systemd/readahead", 0755) && errno != EEXIST)
207                 log_warning("Failed to create /run/systemd: %m");
208 }
209
210 int open_inotify(void) {
211         int fd;
212
213         fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
214         if (fd < 0) {
215                 log_error("Failed to create inotify handle: %m");
216                 return -errno;
217         }
218
219         mkdirs();
220
221         if (inotify_add_watch(fd, "/run/systemd/readahead", IN_CREATE) < 0) {
222                 log_error("Failed to watch /run/systemd/readahead: %m");
223                 close_nointr_nofail(fd);
224                 return -errno;
225         }
226
227         return fd;
228 }
229
230 ReadaheadShared *shared_get(void) {
231         int _cleanup_close_ fd = -1;
232         ReadaheadShared *m = NULL;
233
234         mkdirs();
235
236         fd = open("/run/systemd/readahead/shared", O_CREAT|O_RDWR|O_CLOEXEC, 0644);
237         if (fd < 0) {
238                 log_error("Failed to create shared memory segment: %m");
239                 return NULL;
240         }
241
242         if (ftruncate(fd, sizeof(ReadaheadShared)) < 0) {
243                 log_error("Failed to truncate shared memory segment: %m");
244                 return NULL;
245         }
246
247         m = mmap(NULL, sizeof(ReadaheadShared), PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
248         if (m == MAP_FAILED) {
249                 log_error("Failed to mmap shared memory segment: %m");
250                 return NULL;
251         }
252
253         return m;
254 }
255
256 /* We use 20K instead of the more human digestable 16K here. Why?
257    Simply so that it is more unlikely that users end up picking this
258    value too so that we can recognize better whether the user changed
259    the value while we had it temporarily bumped. */
260 #define BUMP_REQUEST_NR (20*1024)
261
262 int block_bump_request_nr(const char *p) {
263         struct stat st;
264         uint64_t u;
265         char *ap = NULL, *line = NULL;
266         int r;
267         dev_t d;
268
269         assert(p);
270
271         if (stat(p, &st) < 0)
272                 return -errno;
273
274         if (major(st.st_dev) == 0)
275                 return 0;
276
277         d = st.st_dev;
278         block_get_whole_disk(d, &d);
279
280         if (asprintf(&ap, "/sys/dev/block/%u:%u/queue/nr_requests", major(d), minor(d)) < 0) {
281                 r= -ENOMEM;
282                 goto finish;
283         }
284
285         r = read_one_line_file(ap, &line);
286         if (r < 0) {
287                 if (r == -ENOENT)
288                         r = 0;
289                 goto finish;
290         }
291
292         r = safe_atou64(line, &u);
293         if (r >= 0 && u >= BUMP_REQUEST_NR) {
294                 r = 0;
295                 goto finish;
296         }
297
298         free(line);
299         line = NULL;
300
301         if (asprintf(&line, "%lu", (unsigned long) BUMP_REQUEST_NR) < 0) {
302                 r = -ENOMEM;
303                 goto finish;
304         }
305
306         r = write_string_file(ap, line);
307         if (r < 0)
308                 goto finish;
309
310         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);
311         r = 1;
312
313 finish:
314         free(ap);
315         free(line);
316
317         return r;
318 }
319
320 int block_get_readahead(const char *p, uint64_t *bytes) {
321         struct stat st;
322         char *ap = NULL, *line = NULL;
323         int r;
324         dev_t d;
325         uint64_t u;
326
327         assert(p);
328         assert(bytes);
329
330         if (stat(p, &st) < 0)
331                 return -errno;
332
333         if (major(st.st_dev) == 0)
334                 return 0;
335
336         d = st.st_dev;
337         block_get_whole_disk(d, &d);
338
339         if (asprintf(&ap, "/sys/dev/block/%u:%u/bdi/read_ahead_kb", major(d), minor(d)) < 0) {
340                 r = -ENOMEM;
341                 goto finish;
342         }
343
344         r = read_one_line_file(ap, &line);
345         if (r < 0)
346                 goto finish;
347
348         r = safe_atou64(line, &u);
349         if (r < 0)
350                 goto finish;
351
352         *bytes = u * 1024ULL;
353
354 finish:
355         free(ap);
356         free(line);
357
358         return r;
359 }
360
361 int block_set_readahead(const char *p, uint64_t bytes) {
362         struct stat st;
363         char *ap = NULL, *line = NULL;
364         int r;
365         dev_t d;
366
367         assert(p);
368         assert(bytes);
369
370         if (stat(p, &st) < 0)
371                 return -errno;
372
373         if (major(st.st_dev) == 0)
374                 return 0;
375
376         d = st.st_dev;
377         block_get_whole_disk(d, &d);
378
379         if (asprintf(&ap, "/sys/dev/block/%u:%u/bdi/read_ahead_kb", major(d), minor(d)) < 0) {
380                 r = -ENOMEM;
381                 goto finish;
382         }
383
384         if (asprintf(&line, "%llu", (unsigned long long) bytes / 1024ULL) < 0) {
385                 r = -ENOMEM;
386                 goto finish;
387         }
388
389         r = write_string_file(ap, line);
390         if (r < 0)
391                 goto finish;
392
393 finish:
394         free(ap);
395         free(line);
396
397         return r;
398 }