]> git.proxmox.com Git - mirror_lxcfs.git/blob - src/proc_fuse.c
Merge pull request #426 from alliedtelesis/add_fuse3_support
[mirror_lxcfs.git] / src / proc_fuse.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE
5 #endif
6
7 #include "config.h"
8
9 #ifdef HAVE_FUSE3
10 #ifndef FUSE_USE_VERSION
11 #define FUSE_USE_VERSION 30
12 #endif
13 #else
14 #ifndef FUSE_USE_VERSION
15 #define FUSE_USE_VERSION 26
16 #endif
17 #endif
18
19 #define _FILE_OFFSET_BITS 64
20
21 #define __STDC_FORMAT_MACROS
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <fuse.h>
26 #include <inttypes.h>
27 #include <libgen.h>
28 #include <pthread.h>
29 #include <sched.h>
30 #include <stdarg.h>
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include <wait.h>
39 #include <linux/magic.h>
40 #include <linux/sched.h>
41 #include <sys/epoll.h>
42 #include <sys/mman.h>
43 #include <sys/mount.h>
44 #include <sys/param.h>
45 #include <sys/socket.h>
46 #include <sys/syscall.h>
47 #include <sys/sysinfo.h>
48 #include <sys/vfs.h>
49
50 #include "bindings.h"
51 #include "cgroup_fuse.h"
52 #include "cgroups/cgroup.h"
53 #include "cgroups/cgroup_utils.h"
54 #include "cpuset_parse.h"
55 #include "lxcfs_fuse_compat.h"
56 #include "memory_utils.h"
57 #include "proc_loadavg.h"
58 #include "proc_cpuview.h"
59 #include "utils.h"
60
61 struct memory_stat {
62 uint64_t hierarchical_memory_limit;
63 uint64_t hierarchical_memsw_limit;
64 uint64_t total_cache;
65 uint64_t total_rss;
66 uint64_t total_rss_huge;
67 uint64_t total_shmem;
68 uint64_t total_mapped_file;
69 uint64_t total_dirty;
70 uint64_t total_writeback;
71 uint64_t total_swap;
72 uint64_t total_pgpgin;
73 uint64_t total_pgpgout;
74 uint64_t total_pgfault;
75 uint64_t total_pgmajfault;
76 uint64_t total_inactive_anon;
77 uint64_t total_active_anon;
78 uint64_t total_inactive_file;
79 uint64_t total_active_file;
80 uint64_t total_unevictable;
81 };
82
83 __lxcfs_fuse_ops int proc_getattr(const char *path, struct stat *sb)
84 {
85 struct timespec now;
86
87 memset(sb, 0, sizeof(struct stat));
88 if (clock_gettime(CLOCK_REALTIME, &now) < 0)
89 return -EINVAL;
90
91 sb->st_uid = sb->st_gid = 0;
92 sb->st_atim = sb->st_mtim = sb->st_ctim = now;
93 if (strcmp(path, "/proc") == 0) {
94 sb->st_mode = S_IFDIR | 00555;
95 sb->st_nlink = 2;
96 return 0;
97 }
98
99 if (strcmp(path, "/proc/meminfo") == 0 ||
100 strcmp(path, "/proc/cpuinfo") == 0 ||
101 strcmp(path, "/proc/uptime") == 0 ||
102 strcmp(path, "/proc/stat") == 0 ||
103 strcmp(path, "/proc/diskstats") == 0 ||
104 strcmp(path, "/proc/swaps") == 0 ||
105 strcmp(path, "/proc/loadavg") == 0) {
106 sb->st_size = 4096;
107 sb->st_mode = S_IFREG | 00444;
108 sb->st_nlink = 1;
109 return 0;
110 }
111
112 return -ENOENT;
113 }
114
115 __lxcfs_fuse_ops int proc_readdir(const char *path, void *buf,
116 fuse_fill_dir_t filler, off_t offset,
117 struct fuse_file_info *fi)
118 {
119 if (DIR_FILLER(filler, buf, ".", NULL, 0) != 0 ||
120 DIR_FILLER(filler, buf, "..", NULL, 0) != 0 ||
121 DIR_FILLER(filler, buf, "cpuinfo", NULL, 0) != 0 ||
122 DIR_FILLER(filler, buf, "meminfo", NULL, 0) != 0 ||
123 DIR_FILLER(filler, buf, "stat", NULL, 0) != 0 ||
124 DIR_FILLER(filler, buf, "uptime", NULL, 0) != 0 ||
125 DIR_FILLER(filler, buf, "diskstats", NULL, 0) != 0 ||
126 DIR_FILLER(filler, buf, "swaps", NULL, 0) != 0 ||
127 DIR_FILLER(filler, buf, "loadavg", NULL, 0) != 0)
128 return -EINVAL;
129
130 return 0;
131 }
132
133 static off_t get_procfile_size(const char *path)
134 {
135 __do_fclose FILE *f = NULL;
136 __do_free char *line = NULL;
137 size_t len = 0;
138 ssize_t sz, answer = 0;
139
140 f = fopen(path, "re");
141 if (!f)
142 return 0;
143
144 while ((sz = getline(&line, &len, f)) != -1)
145 answer += sz;
146
147 return answer;
148 }
149
150 __lxcfs_fuse_ops int proc_open(const char *path, struct fuse_file_info *fi)
151 {
152 __do_free struct file_info *info = NULL;
153 int type = -1;
154
155 if (strcmp(path, "/proc/meminfo") == 0)
156 type = LXC_TYPE_PROC_MEMINFO;
157 else if (strcmp(path, "/proc/cpuinfo") == 0)
158 type = LXC_TYPE_PROC_CPUINFO;
159 else if (strcmp(path, "/proc/uptime") == 0)
160 type = LXC_TYPE_PROC_UPTIME;
161 else if (strcmp(path, "/proc/stat") == 0)
162 type = LXC_TYPE_PROC_STAT;
163 else if (strcmp(path, "/proc/diskstats") == 0)
164 type = LXC_TYPE_PROC_DISKSTATS;
165 else if (strcmp(path, "/proc/swaps") == 0)
166 type = LXC_TYPE_PROC_SWAPS;
167 else if (strcmp(path, "/proc/loadavg") == 0)
168 type = LXC_TYPE_PROC_LOADAVG;
169 if (type == -1)
170 return -ENOENT;
171
172 info = zalloc(sizeof(*info));
173 if (!info)
174 return -ENOMEM;
175
176 info->type = type;
177
178 info->buflen = get_procfile_size(path) + BUF_RESERVE_SIZE;
179
180 info->buf = zalloc(info->buflen);
181 if (!info->buf)
182 return -ENOMEM;
183 /* set actual size to buffer size */
184 info->size = info->buflen;
185
186 fi->fh = PTR_TO_UINT64(move_ptr(info));
187 return 0;
188 }
189
190 __lxcfs_fuse_ops int proc_access(const char *path, int mask)
191 {
192 if (strcmp(path, "/proc") == 0 && access(path, R_OK) == 0)
193 return 0;
194
195 /* these are all read-only */
196 if ((mask & ~R_OK) != 0)
197 return -EACCES;
198
199 return 0;
200 }
201
202 __lxcfs_fuse_ops int proc_release(const char *path, struct fuse_file_info *fi)
203 {
204 do_release_file_info(fi);
205 return 0;
206 }
207
208 static uint64_t get_memlimit(const char *cgroup, bool swap)
209 {
210 __do_free char *memlimit_str = NULL;
211 uint64_t memlimit = 0;
212 int ret;
213
214 if (swap)
215 ret = cgroup_ops->get_memory_swap_max(cgroup_ops, cgroup, &memlimit_str);
216 else
217 ret = cgroup_ops->get_memory_max(cgroup_ops, cgroup, &memlimit_str);
218 if (ret > 0 && memlimit_str[0] && safe_uint64(memlimit_str, &memlimit, 10) < 0)
219 lxcfs_error("Failed to convert memlimit %s", memlimit_str);
220
221 return memlimit;
222 }
223
224 /*
225 * This function taken from glibc-2.32, as POSIX dirname("/some-dir") will
226 * return "/some-dir" as opposed to "/", which breaks `get_min_memlimit()`
227 */
228 static char *gnu_dirname(char *path)
229 {
230 static const char dot[] = ".";
231 char *last_slash;
232
233 /* Find last '/'. */
234 last_slash = path != NULL ? strrchr(path, '/') : NULL;
235
236 if (last_slash != NULL && last_slash != path && last_slash[1] == '\0') {
237 /* Determine whether all remaining characters are slashes. */
238 char *runp;
239
240 for (runp = last_slash; runp != path; --runp)
241 if (runp[-1] != '/')
242 break;
243
244 /* The '/' is the last character, we have to look further. */
245 if (runp != path)
246 last_slash = memrchr(path, '/', runp - path);
247 }
248
249 if (last_slash != NULL) {
250 /* Determine whether all remaining characters are slashes. */
251 char *runp;
252
253 for (runp = last_slash; runp != path; --runp)
254 if (runp[-1] != '/')
255 break;
256
257 /* Terminate the path. */
258 if (runp == path) {
259 /*
260 * The last slash is the first character in the string.
261 * We have to return "/". As a special case we have to
262 * return "//" if there are exactly two slashes at the
263 * beginning of the string. See XBD 4.10 Path Name
264 * Resolution for more information
265 */
266 if (last_slash == path + 1)
267 ++last_slash;
268 else
269 last_slash = path + 1;
270 } else
271 last_slash = runp;
272
273 last_slash[0] = '\0';
274 } else {
275 /*
276 * This assignment is ill-designed but the XPG specs require to
277 * return a string containing "." in any case no directory part
278 * is found and so a static and constant string is required.
279 */
280 path = (char *)dot;
281 }
282
283 return path;
284 }
285
286 static uint64_t get_min_memlimit(const char *cgroup, bool swap)
287 {
288 __do_free char *copy = NULL;
289 uint64_t memlimit = 0, retlimit = 0;
290
291 copy = strdup(cgroup);
292 if (!copy)
293 return log_error_errno(0, ENOMEM, "Failed to allocate memory");
294
295 retlimit = get_memlimit(copy, swap);
296
297 /*
298 * If the cgroup doesn't start with / (probably won't happen), dirname()
299 * will terminate with "" instead of "/"
300 */
301 while (*copy && strcmp(copy, "/") != 0) {
302 char *it = copy;
303
304 it = gnu_dirname(it);
305 memlimit = get_memlimit(it, swap);
306 if (memlimit > 0 && memlimit < retlimit)
307 retlimit = memlimit;
308 };
309
310 return retlimit;
311 }
312
313 static inline bool startswith(const char *line, const char *pref)
314 {
315 return strncmp(line, pref, strlen(pref)) == 0;
316 }
317
318 static int proc_swaps_read(char *buf, size_t size, off_t offset,
319 struct fuse_file_info *fi)
320 {
321 __do_free char *cgroup = NULL, *memusage_str = NULL, *memswusage_str = NULL;
322 struct fuse_context *fc = fuse_get_context();
323 struct lxcfs_opts *opts = (struct lxcfs_opts *)fuse_get_context()->private_data;
324 bool wants_swap = opts && !opts->swap_off && liblxcfs_can_use_swap();
325 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
326 uint64_t memswlimit = 0, memlimit = 0, memusage = 0, memswusage = 0,
327 swtotal = 0, swfree = 0, swusage = 0;
328 ssize_t total_len = 0;
329 ssize_t l = 0;
330 char *cache = d->buf;
331 int ret;
332
333 if (offset) {
334 int left;
335
336 if (offset > d->size)
337 return -EINVAL;
338
339 if (!d->cached)
340 return 0;
341
342 left = d->size - offset;
343 total_len = left > size ? size: left;
344 memcpy(buf, cache + offset, total_len);
345
346 return total_len;
347 }
348
349 pid_t initpid = lookup_initpid_in_store(fc->pid);
350 if (initpid <= 1 || is_shared_pidns(initpid))
351 initpid = fc->pid;
352
353 cgroup = get_pid_cgroup(initpid, "memory");
354 if (!cgroup)
355 return read_file_fuse("/proc/swaps", buf, size, d);
356 prune_init_slice(cgroup);
357
358 memlimit = get_min_memlimit(cgroup, false);
359
360 ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
361 if (ret < 0)
362 return 0;
363
364 if (safe_uint64(memusage_str, &memusage, 10) < 0)
365 lxcfs_error("Failed to convert memusage %s", memusage_str);
366
367 if (wants_swap) {
368 memswlimit = get_min_memlimit(cgroup, true);
369 if (memswlimit > 0) {
370 ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
371 if (ret >= 0 && safe_uint64(memswusage_str, &memswusage, 10) == 0) {
372 if (memlimit > memswlimit)
373 swtotal = 0;
374 else
375 swtotal = (memswlimit - memlimit) / 1024;
376 if (memusage > memswusage || swtotal == 0)
377 swusage = 0;
378 else
379 swusage = (memswusage - memusage) / 1024;
380 if (swtotal >= swusage)
381 swfree = swtotal - swusage;
382 }
383 }
384 }
385
386 total_len = snprintf(d->buf, d->size, "Filename\t\t\t\tType\t\tSize\tUsed\tPriority\n");
387
388 /* When no mem + swap limit is specified or swapaccount=0*/
389 if (!memswlimit) {
390 __do_free char *line = NULL;
391 __do_free void *fopen_cache = NULL;
392 __do_fclose FILE *f = NULL;
393 size_t linelen = 0;
394
395 f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
396 if (!f)
397 return 0;
398
399 while (getline(&line, &linelen, f) != -1) {
400 if (startswith(line, "SwapTotal:"))
401 sscanf(line, "SwapTotal: %8" PRIu64 " kB", &swtotal);
402 else if (startswith(line, "SwapFree:"))
403 sscanf(line, "SwapFree: %8" PRIu64 " kB", &swfree);
404 }
405 }
406
407 if (swtotal > 0) {
408 l = snprintf(d->buf + total_len, d->size - total_len,
409 "none%*svirtual\t\t%" PRIu64 "\t%" PRIu64 "\t0\n",
410 36, " ", swtotal, swfree);
411 total_len += l;
412 }
413
414 if (total_len < 0 || l < 0)
415 return log_error(0, "Failed writing to cache");
416
417 d->cached = 1;
418 d->size = (int)total_len;
419
420 if (total_len > size)
421 total_len = size;
422 memcpy(buf, d->buf, total_len);
423
424 return total_len;
425 }
426
427 static void get_blkio_io_value(char *str, unsigned major, unsigned minor,
428 char *iotype, uint64_t *v)
429 {
430 char *eol;
431 char key[32];
432 size_t len;
433
434 memset(key, 0, 32);
435 snprintf(key, 32, "%u:%u %s", major, minor, iotype);
436
437 *v = 0;
438 len = strlen(key);
439 while (*str) {
440 if (startswith(str, key)) {
441 sscanf(str + len, "%lu", v);
442 return;
443 }
444 eol = strchr(str, '\n');
445 if (!eol)
446 return;
447 str = eol+1;
448 }
449 }
450
451 static int proc_diskstats_read(char *buf, size_t size, off_t offset,
452 struct fuse_file_info *fi)
453 {
454 __do_free char *cg = NULL, *io_serviced_str = NULL,
455 *io_merged_str = NULL, *io_service_bytes_str = NULL,
456 *io_wait_time_str = NULL, *io_service_time_str = NULL,
457 *line = NULL;
458 __do_free void *fopen_cache = NULL;
459 __do_fclose FILE *f = NULL;
460 struct fuse_context *fc = fuse_get_context();
461 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
462 uint64_t read = 0, write = 0;
463 uint64_t read_merged = 0, write_merged = 0;
464 uint64_t read_sectors = 0, write_sectors = 0;
465 uint64_t read_ticks = 0, write_ticks = 0;
466 uint64_t ios_pgr = 0, tot_ticks = 0, rq_ticks = 0;
467 uint64_t rd_svctm = 0, wr_svctm = 0, rd_wait = 0, wr_wait = 0;
468 char *cache = d->buf;
469 size_t cache_size = d->buflen;
470 size_t linelen = 0, total_len = 0;
471 unsigned int major = 0, minor = 0;
472 int i = 0;
473 int ret;
474 char dev_name[72];
475
476 if (offset) {
477 int left;
478
479 if (offset > d->size)
480 return -EINVAL;
481
482 if (!d->cached)
483 return 0;
484
485 left = d->size - offset;
486 total_len = left > size ? size: left;
487 memcpy(buf, cache + offset, total_len);
488
489 return total_len;
490 }
491
492 pid_t initpid = lookup_initpid_in_store(fc->pid);
493 if (initpid <= 1 || is_shared_pidns(initpid))
494 initpid = fc->pid;
495
496 cg = get_pid_cgroup(initpid, "blkio");
497 if (!cg)
498 return read_file_fuse("/proc/diskstats", buf, size, d);
499 prune_init_slice(cg);
500
501 ret = cgroup_ops->get_io_serviced(cgroup_ops, cg, &io_serviced_str);
502 if (ret < 0) {
503 if (ret == -EOPNOTSUPP)
504 return read_file_fuse("/proc/diskstats", buf, size, d);
505 }
506
507 ret = cgroup_ops->get_io_merged(cgroup_ops, cg, &io_merged_str);
508 if (ret < 0) {
509 if (ret == -EOPNOTSUPP)
510 return read_file_fuse("/proc/diskstats", buf, size, d);
511 }
512
513 ret = cgroup_ops->get_io_service_bytes(cgroup_ops, cg, &io_service_bytes_str);
514 if (ret < 0) {
515 if (ret == -EOPNOTSUPP)
516 return read_file_fuse("/proc/diskstats", buf, size, d);
517 }
518
519 ret = cgroup_ops->get_io_wait_time(cgroup_ops, cg, &io_wait_time_str);
520 if (ret < 0) {
521 if (ret == -EOPNOTSUPP)
522 return read_file_fuse("/proc/diskstats", buf, size, d);
523 }
524
525 ret = cgroup_ops->get_io_service_time(cgroup_ops, cg, &io_service_time_str);
526 if (ret < 0) {
527 if (ret == -EOPNOTSUPP)
528 return read_file_fuse("/proc/diskstats", buf, size, d);
529 }
530
531 f = fopen_cached("/proc/diskstats", "re", &fopen_cache);
532 if (!f)
533 return 0;
534
535 while (getline(&line, &linelen, f) != -1) {
536 ssize_t l;
537 char lbuf[256];
538
539 i = sscanf(line, "%u %u %71s", &major, &minor, dev_name);
540 if (i != 3)
541 continue;
542
543 get_blkio_io_value(io_serviced_str, major, minor, "Read", &read);
544 get_blkio_io_value(io_serviced_str, major, minor, "Write", &write);
545 get_blkio_io_value(io_merged_str, major, minor, "Read", &read_merged);
546 get_blkio_io_value(io_merged_str, major, minor, "Write", &write_merged);
547 get_blkio_io_value(io_service_bytes_str, major, minor, "Read", &read_sectors);
548 read_sectors = read_sectors/512;
549 get_blkio_io_value(io_service_bytes_str, major, minor, "Write", &write_sectors);
550 write_sectors = write_sectors/512;
551
552 get_blkio_io_value(io_service_time_str, major, minor, "Read", &rd_svctm);
553 rd_svctm = rd_svctm/1000000;
554 get_blkio_io_value(io_wait_time_str, major, minor, "Read", &rd_wait);
555 rd_wait = rd_wait/1000000;
556 read_ticks = rd_svctm + rd_wait;
557
558 get_blkio_io_value(io_service_time_str, major, minor, "Write", &wr_svctm);
559 wr_svctm = wr_svctm/1000000;
560 get_blkio_io_value(io_wait_time_str, major, minor, "Write", &wr_wait);
561 wr_wait = wr_wait/1000000;
562 write_ticks = wr_svctm + wr_wait;
563
564 get_blkio_io_value(io_service_time_str, major, minor, "Total", &tot_ticks);
565 tot_ticks = tot_ticks/1000000;
566
567 memset(lbuf, 0, 256);
568 if (read || write || read_merged || write_merged || read_sectors || write_sectors || read_ticks || write_ticks)
569 snprintf(lbuf, 256, "%u %u %s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
570 major, minor, dev_name, read, read_merged, read_sectors, read_ticks,
571 write, write_merged, write_sectors, write_ticks, ios_pgr, tot_ticks, rq_ticks);
572 else
573 continue;
574
575 l = snprintf(cache, cache_size, "%s", lbuf);
576 if (l < 0)
577 return log_error(0, "Failed to write cache");
578 if (l >= cache_size)
579 return log_error(0, "Write to cache was truncated");
580
581 cache += l;
582 cache_size -= l;
583 total_len += l;
584 }
585
586 d->cached = 1;
587 d->size = total_len;
588 if (total_len > size)
589 total_len = size;
590 memcpy(buf, d->buf, total_len);
591
592 return total_len;
593 }
594
595 #if RELOADTEST
596 static inline void iwashere(void)
597 {
598 mknod("/tmp/lxcfs-iwashere", S_IFREG, 0644);
599 }
600 #endif
601
602 /*
603 * This function retrieves the busy time of a group of tasks by looking at
604 * cpuacct.usage. Unfortunately, this only makes sense when the container has
605 * been given it's own cpuacct cgroup. If not, this function will take the busy
606 * time of all other taks that do not actually belong to the container into
607 * account as well. If someone has a clever solution for this please send a
608 * patch!
609 */
610 static double get_reaper_busy(pid_t task)
611 {
612 __do_free char *cgroup = NULL, *usage_str = NULL;
613 uint64_t usage = 0;
614 pid_t initpid;
615
616 initpid = lookup_initpid_in_store(task);
617 if (initpid <= 0)
618 return 0;
619
620 cgroup = get_pid_cgroup(initpid, "cpuacct");
621 if (!cgroup)
622 return 0;
623 prune_init_slice(cgroup);
624
625 if (!cgroup_ops->get(cgroup_ops, "cpuacct", cgroup, "cpuacct.usage", &usage_str))
626 return 0;
627
628 if (safe_uint64(usage_str, &usage, 10) < 0)
629 lxcfs_error("Failed to convert usage %s", usage_str);
630
631 return ((double)usage / 1000000000);
632 }
633
634 static uint64_t get_reaper_start_time(pid_t pid)
635 {
636 __do_free void *fopen_cache = NULL;
637 __do_fclose FILE *f = NULL;
638 int ret;
639 uint64_t starttime;
640 char path[STRLITERALLEN("/proc/") + LXCFS_NUMSTRLEN64 +
641 STRLITERALLEN("/stat") + 1];
642 pid_t qpid;
643
644 qpid = lookup_initpid_in_store(pid);
645 if (qpid <= 0)
646 return ret_errno(EINVAL);
647
648 ret = snprintf(path, sizeof(path), "/proc/%d/stat", qpid);
649 if (ret < 0 || (size_t)ret >= sizeof(path))
650 return ret_errno(EINVAL);
651
652 f = fopen_cached(path, "re", &fopen_cache);
653 if (!f)
654 return ret_errno(EINVAL);
655
656 /* Note that the *scanf() argument supression requires that length
657 * modifiers such as "l" are omitted. Otherwise some compilers will yell
658 * at us. It's like telling someone you're not married and then asking
659 * if you can bring your wife to the party.
660 */
661 ret = fscanf(f, "%*d " /* (1) pid %d */
662 "%*s " /* (2) comm %s */
663 "%*c " /* (3) state %c */
664 "%*d " /* (4) ppid %d */
665 "%*d " /* (5) pgrp %d */
666 "%*d " /* (6) session %d */
667 "%*d " /* (7) tty_nr %d */
668 "%*d " /* (8) tpgid %d */
669 "%*u " /* (9) flags %u */
670 "%*u " /* (10) minflt %lu */
671 "%*u " /* (11) cminflt %lu */
672 "%*u " /* (12) majflt %lu */
673 "%*u " /* (13) cmajflt %lu */
674 "%*u " /* (14) utime %lu */
675 "%*u " /* (15) stime %lu */
676 "%*d " /* (16) cutime %ld */
677 "%*d " /* (17) cstime %ld */
678 "%*d " /* (18) priority %ld */
679 "%*d " /* (19) nice %ld */
680 "%*d " /* (20) num_threads %ld */
681 "%*d " /* (21) itrealvalue %ld */
682 "%" PRIu64, /* (22) starttime %llu */
683 &starttime);
684 if (ret != 1)
685 return ret_errno(EINVAL);
686
687 return ret_set_errno(starttime, 0);
688 }
689
690 static double get_reaper_start_time_in_sec(pid_t pid)
691 {
692 uint64_t clockticks, ticks_per_sec;
693 int64_t ret;
694 double res = 0;
695
696 clockticks = get_reaper_start_time(pid);
697 if (clockticks <= 0)
698 return log_debug(0, "Failed to retrieve start time of pid %d", pid);
699
700 ret = sysconf(_SC_CLK_TCK);
701 if (ret < 0)
702 return log_debug(0, "Failed to determine number of clock ticks in a second");
703
704 ticks_per_sec = (uint64_t)ret;
705 res = (double)clockticks / ticks_per_sec;
706 return res;
707 }
708
709 static double get_reaper_age(pid_t pid)
710 {
711 uint64_t uptime_ms;
712 double procstart, procage;
713
714 /*
715 * We need to substract the time the process has started since system
716 * boot minus the time when the system has started to get the actual
717 * reaper age.
718 */
719 procstart = get_reaper_start_time_in_sec(pid);
720 procage = procstart;
721 if (procstart > 0) {
722 int ret;
723 struct timespec spec;
724
725 ret = clock_gettime(CLOCK_BOOTTIME, &spec);
726 if (ret < 0)
727 return 0;
728
729 uptime_ms = (spec.tv_sec * 1000) + (spec.tv_nsec * 1e-6);
730 procage = (uptime_ms - (procstart * 1000)) / 1000;
731 }
732
733 return procage;
734 }
735
736 /*
737 * We read /proc/uptime and reuse its second field.
738 * For the first field, we use the mtime for the reaper for
739 * the calling pid as returned by getreaperage
740 */
741 static int proc_uptime_read(char *buf, size_t size, off_t offset,
742 struct fuse_file_info *fi)
743 {
744 struct fuse_context *fc = fuse_get_context();
745 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
746 char *cache = d->buf;
747 ssize_t total_len = 0, ret = 0;
748 double busytime, idletime, reaperage;
749
750 #if RELOADTEST
751 iwashere();
752 #endif
753
754 if (offset) {
755 int left;
756
757 if (offset > d->size)
758 return -EINVAL;
759
760 if (!d->cached)
761 return 0;
762
763 left = d->size - offset;
764 total_len = left > size ? size : left;
765 memcpy(buf, cache + offset, total_len);
766
767 return total_len;
768 }
769
770 reaperage = get_reaper_age(fc->pid);
771 /*
772 * To understand why this is done, please read the comment to the
773 * get_reaper_busy() function.
774 */
775 idletime = reaperage;
776 busytime = get_reaper_busy(fc->pid);
777 if (reaperage >= busytime)
778 idletime = reaperage - busytime;
779
780 ret = snprintf(d->buf, d->buflen, "%.2lf %.2lf\n", reaperage, idletime);
781 if (ret < 0 || ret >= d->buflen)
782 return read_file_fuse("/proc/uptime", buf, size, d);
783 total_len = ret;
784
785 d->cached = 1;
786 d->size = total_len;
787 if (total_len > size)
788 total_len = size;
789 memcpy(buf, d->buf, total_len);
790
791 return total_len;
792 }
793
794 #define CPUALL_MAX_SIZE (BUF_RESERVE_SIZE / 2)
795 static int proc_stat_read(char *buf, size_t size, off_t offset,
796 struct fuse_file_info *fi)
797 {
798 __do_free char *cg = NULL, *cpuset = NULL, *line = NULL;
799 __do_free void *fopen_cache = NULL;
800 __do_free struct cpuacct_usage *cg_cpu_usage = NULL;
801 __do_fclose FILE *f = NULL;
802 struct fuse_context *fc = fuse_get_context();
803 struct lxcfs_opts *opts = (struct lxcfs_opts *)fc->private_data;
804 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
805 size_t linelen = 0, total_len = 0;
806 int curcpu = -1; /* cpu numbering starts at 0 */
807 int physcpu = 0;
808 uint64_t user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0,
809 softirq = 0, steal = 0, guest = 0, guest_nice = 0;
810 uint64_t user_sum = 0, nice_sum = 0, system_sum = 0, idle_sum = 0,
811 iowait_sum = 0, irq_sum = 0, softirq_sum = 0, steal_sum = 0,
812 guest_sum = 0, guest_nice_sum = 0;
813 char cpuall[CPUALL_MAX_SIZE];
814 /* reserve for cpu all */
815 char *cache = d->buf + CPUALL_MAX_SIZE;
816 size_t cache_size = d->buflen - CPUALL_MAX_SIZE;
817 int cg_cpu_usage_size = 0;
818
819 if (offset) {
820 int left;
821
822 if (offset > d->size)
823 return -EINVAL;
824
825 if (!d->cached)
826 return 0;
827
828 left = d->size - offset;
829 total_len = left > size ? size : left;
830 memcpy(buf, d->buf + offset, total_len);
831
832 return total_len;
833 }
834
835 pid_t initpid = lookup_initpid_in_store(fc->pid);
836 if (initpid <= 1 || is_shared_pidns(initpid))
837 initpid = fc->pid;
838
839 /*
840 * when container run with host pid namespace initpid == 1, cgroup will "/"
841 * we should return host os's /proc contents.
842 * in some case cpuacct_usage.all in "/" will larger then /proc/stat
843 */
844 if (initpid == 1)
845 return read_file_fuse("/proc/stat", buf, size, d);
846
847 cg = get_pid_cgroup(initpid, "cpuset");
848 if (!cg)
849 return read_file_fuse("/proc/stat", buf, size, d);
850 prune_init_slice(cg);
851
852 cpuset = get_cpuset(cg);
853 if (!cpuset)
854 return 0;
855
856 f = fopen_cached("/proc/stat", "re", &fopen_cache);
857 if (!f)
858 return 0;
859
860 /* Skip first system cpu line. */
861 if (getline(&line, &linelen, f) < 0)
862 return log_error(0, "proc_stat_read read first line failed");
863
864 /*
865 * Read cpuacct.usage_all for all CPUs.
866 * If the cpuacct cgroup is present, it is used to calculate the container's
867 * CPU usage. If not, values from the host's /proc/stat are used.
868 */
869 if (read_cpuacct_usage_all(cg, cpuset, &cg_cpu_usage, &cg_cpu_usage_size) == 0) {
870 if (cgroup_ops->can_use_cpuview(cgroup_ops) && opts && opts->use_cfs) {
871 total_len = cpuview_proc_stat(cg, cpuset, cg_cpu_usage,
872 cg_cpu_usage_size, f,
873 d->buf, d->buflen);
874 goto out;
875 }
876 } else {
877 lxcfs_v("proc_stat_read failed to read from cpuacct, falling back to the host's /proc/stat");
878 }
879
880 while (getline(&line, &linelen, f) != -1) {
881 ssize_t l;
882 char cpu_char[10]; /* That's a lot of cores */
883 char *c;
884 uint64_t all_used, cg_used, new_idle;
885 int ret;
886
887 if (strlen(line) == 0)
888 continue;
889 if (sscanf(line, "cpu%9[^ ]", cpu_char) != 1) {
890 /* not a ^cpuN line containing a number N, just print it */
891 l = snprintf(cache, cache_size, "%s", line);
892 if (l < 0)
893 return log_error(0, "Failed to write cache");
894 if (l >= cache_size)
895 return log_error(0, "Write to cache was truncated");
896
897 cache += l;
898 cache_size -= l;
899 total_len += l;
900
901 continue;
902 }
903
904 if (sscanf(cpu_char, "%d", &physcpu) != 1)
905 continue;
906
907 if (!cpu_in_cpuset(physcpu, cpuset))
908 continue;
909
910 curcpu++;
911
912 ret = sscanf(line, "%*s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
913 &user,
914 &nice,
915 &system,
916 &idle,
917 &iowait,
918 &irq,
919 &softirq,
920 &steal,
921 &guest,
922 &guest_nice);
923 if (ret != 10 || !cg_cpu_usage) {
924 c = strchr(line, ' ');
925 if (!c)
926 continue;
927
928 l = snprintf(cache, cache_size, "cpu%d%s", curcpu, c);
929 if (l < 0)
930 return log_error(0, "Failed to write cache");
931 if (l >= cache_size)
932 return log_error(0, "Write to cache was truncated");
933
934 cache += l;
935 cache_size -= l;
936 total_len += l;
937
938 if (ret != 10)
939 continue;
940 }
941
942 if (cg_cpu_usage) {
943 if (physcpu >= cg_cpu_usage_size)
944 break;
945
946 all_used = user + nice + system + iowait + irq + softirq + steal + guest + guest_nice;
947 cg_used = cg_cpu_usage[physcpu].user + cg_cpu_usage[physcpu].system;
948
949 if (all_used >= cg_used) {
950 new_idle = idle + (all_used - cg_used);
951
952 } else {
953 lxcfs_error("cpu%d from %s has unexpected cpu time: %" PRIu64 " in /proc/stat, %" PRIu64 " in cpuacct.usage_all; unable to determine idle time",
954 curcpu, cg, all_used, cg_used);
955 new_idle = idle;
956 }
957
958 l = snprintf(cache, cache_size,
959 "cpu%d %" PRIu64 " 0 %" PRIu64 " %" PRIu64 " 0 0 0 0 0 0\n",
960 curcpu, cg_cpu_usage[physcpu].user,
961 cg_cpu_usage[physcpu].system, new_idle);
962 if (l < 0)
963 return log_error(0, "Failed to write cache");
964 if (l >= cache_size)
965 return log_error(0, "Write to cache was truncated");
966
967 cache += l;
968 cache_size -= l;
969 total_len += l;
970
971 user_sum += cg_cpu_usage[physcpu].user;
972 system_sum += cg_cpu_usage[physcpu].system;
973 idle_sum += new_idle;
974 } else {
975 user_sum += user;
976 nice_sum += nice;
977 system_sum += system;
978 idle_sum += idle;
979 iowait_sum += iowait;
980 irq_sum += irq;
981 softirq_sum += softirq;
982 steal_sum += steal;
983 guest_sum += guest;
984 guest_nice_sum += guest_nice;
985 }
986 }
987
988 cache = d->buf;
989
990 int cpuall_len = snprintf(cpuall, CPUALL_MAX_SIZE, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
991 user_sum,
992 nice_sum,
993 system_sum,
994 idle_sum,
995 iowait_sum,
996 irq_sum,
997 softirq_sum,
998 steal_sum,
999 guest_sum,
1000 guest_nice_sum);
1001 if (cpuall_len > 0 && cpuall_len < CPUALL_MAX_SIZE) {
1002 memcpy(cache, cpuall, cpuall_len);
1003 cache += cpuall_len;
1004 } else {
1005 /* shouldn't happen */
1006 lxcfs_error("proc_stat_read copy cpuall failed, cpuall_len=%d", cpuall_len);
1007 cpuall_len = 0;
1008 }
1009
1010 memmove(cache, d->buf + CPUALL_MAX_SIZE, total_len);
1011 total_len += cpuall_len;
1012
1013 out:
1014 d->cached = 1;
1015 d->size = total_len;
1016 if (total_len > size)
1017 total_len = size;
1018
1019 memcpy(buf, d->buf, total_len);
1020 return total_len;
1021 }
1022
1023 /* Note that "memory.stat" in cgroup2 is hierarchical by default. */
1024 static bool cgroup_parse_memory_stat(const char *cgroup, struct memory_stat *mstat)
1025 {
1026 __do_close int fd = -EBADF;
1027 __do_fclose FILE *f = NULL;
1028 __do_free char *line = NULL;
1029 __do_free void *fdopen_cache = NULL;
1030 bool unified;
1031 size_t len = 0;
1032 ssize_t linelen;
1033
1034 fd = cgroup_ops->get_memory_stats_fd(cgroup_ops, cgroup);
1035 if (fd < 0)
1036 return false;
1037
1038 f = fdopen_cached(fd, "re", &fdopen_cache);
1039 if (!f)
1040 return false;
1041
1042 unified = pure_unified_layout(cgroup_ops);
1043 while ((linelen = getline(&line, &len, f)) != -1) {
1044 if (!unified && startswith(line, "hierarchical_memory_limit")) {
1045 sscanf(line, "hierarchical_memory_limit %" PRIu64, &(mstat->hierarchical_memory_limit));
1046 } else if (!unified && startswith(line, "hierarchical_memsw_limit")) {
1047 sscanf(line, "hierarchical_memsw_limit %" PRIu64, &(mstat->hierarchical_memsw_limit));
1048 } else if (startswith(line, unified ? "file" :"total_cache")) {
1049 sscanf(line, unified ? "file %" PRIu64 : "total_cache %" PRIu64, &(mstat->total_cache));
1050 } else if (!unified && startswith(line, "total_rss")) {
1051 sscanf(line, "total_rss %" PRIu64, &(mstat->total_rss));
1052 } else if (!unified && startswith(line, "total_rss_huge")) {
1053 sscanf(line, "total_rss_huge %" PRIu64, &(mstat->total_rss_huge));
1054 } else if (startswith(line, unified ? "shmem" : "total_shmem")) {
1055 sscanf(line, unified ? "shmem %" PRIu64 : "total_shmem %" PRIu64, &(mstat->total_shmem));
1056 } else if (startswith(line, unified ? "file_mapped" : "total_mapped_file")) {
1057 sscanf(line, unified ? "file_mapped %" PRIu64 : "total_mapped_file %" PRIu64, &(mstat->total_mapped_file));
1058 } else if (!unified && startswith(line, "total_dirty")) {
1059 sscanf(line, "total_dirty %" PRIu64, &(mstat->total_dirty));
1060 } else if (!unified && startswith(line, "total_writeback")) {
1061 sscanf(line, "total_writeback %" PRIu64, &(mstat->total_writeback));
1062 } else if (!unified && startswith(line, "total_swap")) {
1063 sscanf(line, "total_swap %" PRIu64, &(mstat->total_swap));
1064 } else if (!unified && startswith(line, "total_pgpgin")) {
1065 sscanf(line, "total_pgpgin %" PRIu64, &(mstat->total_pgpgin));
1066 } else if (!unified && startswith(line, "total_pgpgout")) {
1067 sscanf(line, "total_pgpgout %" PRIu64, &(mstat->total_pgpgout));
1068 } else if (startswith(line, unified ? "pgfault" : "total_pgfault")) {
1069 sscanf(line, unified ? "pgfault %" PRIu64 : "total_pgfault %" PRIu64, &(mstat->total_pgfault));
1070 } else if (startswith(line, unified ? "pgmajfault" : "total_pgmajfault")) {
1071 sscanf(line, unified ? "pgmajfault %" PRIu64 : "total_pgmajfault %" PRIu64, &(mstat->total_pgmajfault));
1072 } else if (startswith(line, unified ? "inactive_anon" : "total_inactive_anon")) {
1073 sscanf(line, unified ? "inactive_anon %" PRIu64 : "total_inactive_anon %" PRIu64, &(mstat->total_inactive_anon));
1074 } else if (startswith(line, unified ? "active_anon" : "total_active_anon")) {
1075 sscanf(line, unified ? "active_anon %" PRIu64 : "total_active_anon %" PRIu64, &(mstat->total_active_anon));
1076 } else if (startswith(line, unified ? "inactive_file" : "total_inactive_file")) {
1077 sscanf(line, unified ? "inactive_file %" PRIu64 : "total_inactive_file %" PRIu64, &(mstat->total_inactive_file));
1078 } else if (startswith(line, unified ? "active_file" : "total_active_file")) {
1079 sscanf(line, unified ? "active_file %" PRIu64 : "total_active_file %" PRIu64, &(mstat->total_active_file));
1080 } else if (startswith(line, unified ? "unevictable" : "total_unevictable")) {
1081 sscanf(line, unified ? "unevictable %" PRIu64 : "total_unevictable %" PRIu64, &(mstat->total_unevictable));
1082 }
1083 }
1084
1085 return true;
1086 }
1087
1088 static int proc_meminfo_read(char *buf, size_t size, off_t offset,
1089 struct fuse_file_info *fi)
1090 {
1091 __do_free char *cgroup = NULL, *line = NULL, *memusage_str = NULL,
1092 *memswusage_str = NULL;
1093 __do_free void *fopen_cache = NULL;
1094 __do_fclose FILE *f = NULL;
1095 struct fuse_context *fc = fuse_get_context();
1096 struct lxcfs_opts *opts = (struct lxcfs_opts *)fuse_get_context()->private_data;
1097 bool wants_swap = opts && !opts->swap_off && liblxcfs_can_use_swap(), host_swap = false;
1098 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
1099 uint64_t memlimit = 0, memusage = 0, memswlimit = 0, memswusage = 0,
1100 hosttotal = 0, swfree = 0, swusage = 0, swtotal = 0;
1101 struct memory_stat mstat = {};
1102 size_t linelen = 0, total_len = 0;
1103 char *cache = d->buf;
1104 size_t cache_size = d->buflen;
1105 int ret;
1106
1107 if (offset) {
1108 int left;
1109
1110 if (offset > d->size)
1111 return -EINVAL;
1112
1113 if (!d->cached)
1114 return 0;
1115
1116 left = d->size - offset;
1117 total_len = left > size ? size : left;
1118 memcpy(buf, cache + offset, total_len);
1119
1120 return total_len;
1121 }
1122
1123 pid_t initpid = lookup_initpid_in_store(fc->pid);
1124 if (initpid <= 1 || is_shared_pidns(initpid))
1125 initpid = fc->pid;
1126
1127 cgroup = get_pid_cgroup(initpid, "memory");
1128 if (!cgroup)
1129 return read_file_fuse("/proc/meminfo", buf, size, d);
1130
1131 prune_init_slice(cgroup);
1132
1133 /* memory limits */
1134 ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
1135 if (ret < 0)
1136 return read_file_fuse("/proc/meminfo", buf, size, d);
1137
1138 if (safe_uint64(memusage_str, &memusage, 10) < 0)
1139 lxcfs_error("Failed to convert memusage %s", memusage_str);
1140
1141 if (!cgroup_parse_memory_stat(cgroup, &mstat))
1142 return read_file_fuse("/proc/meminfo", buf, size, d);
1143
1144 memlimit = get_min_memlimit(cgroup, false);
1145
1146 /*
1147 * Following values are allowed to fail, because swapaccount might be
1148 * turned off for current kernel.
1149 */
1150 if (wants_swap) {
1151 memswlimit = get_min_memlimit(cgroup, true);
1152 if (memswlimit > 0) {
1153 ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
1154 if (ret >= 0 && safe_uint64(memswusage_str, &memswusage, 10) == 0) {
1155 if (memlimit > memswlimit)
1156 swtotal = 0;
1157 else
1158 swtotal = (memswlimit - memlimit) / 1024;
1159 if (memusage > memswusage || swtotal == 0)
1160 swusage = 0;
1161 else
1162 swusage = (memswusage - memusage) / 1024;
1163 }
1164 }
1165 }
1166
1167 f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
1168 if (!f)
1169 return read_file_fuse("/proc/meminfo", buf, size, d);
1170
1171 memusage /= 1024;
1172 memlimit /= 1024;
1173 while (getline(&line, &linelen, f) != -1) {
1174 ssize_t l;
1175 char *printme, lbuf[100];
1176
1177 memset(lbuf, 0, 100);
1178 if (startswith(line, "MemTotal:")) {
1179 sscanf(line+sizeof("MemTotal:")-1, "%" PRIu64, &hosttotal);
1180 if (memlimit == 0)
1181 memlimit = hosttotal;
1182
1183 if (hosttotal < memlimit)
1184 memlimit = hosttotal;
1185 snprintf(lbuf, 100, "MemTotal: %8" PRIu64 " kB\n", memlimit);
1186 printme = lbuf;
1187 } else if (startswith(line, "MemFree:")) {
1188 snprintf(lbuf, 100, "MemFree: %8" PRIu64 " kB\n", memlimit - memusage);
1189 printme = lbuf;
1190 } else if (startswith(line, "MemAvailable:")) {
1191 snprintf(lbuf, 100, "MemAvailable: %8" PRIu64 " kB\n", memlimit - memusage + mstat.total_cache / 1024);
1192 printme = lbuf;
1193 } else if (startswith(line, "SwapTotal:")) {
1194 if (wants_swap) {
1195 uint64_t hostswtotal = 0;
1196
1197 sscanf(line + STRLITERALLEN("SwapTotal:"), "%" PRIu64, &hostswtotal);
1198
1199 if (hostswtotal < swtotal) {
1200 swtotal = hostswtotal;
1201 host_swap = true;
1202 }
1203 }
1204
1205 snprintf(lbuf, 100, "SwapTotal: %8" PRIu64 " kB\n", swtotal);
1206 printme = lbuf;
1207 } else if (startswith(line, "SwapFree:")) {
1208 if (wants_swap) {
1209 uint64_t hostswfree = 0;
1210
1211 if (host_swap) {
1212 sscanf(line + STRLITERALLEN("SwapFree:"), "%" PRIu64, &hostswfree);
1213 swfree = hostswfree;
1214 } else if (swtotal >= swusage) {
1215 swfree = swtotal - swusage;
1216 }
1217 }
1218
1219 snprintf(lbuf, 100, "SwapFree: %8" PRIu64 " kB\n", swfree);
1220 printme = lbuf;
1221 } else if (startswith(line, "Slab:")) {
1222 snprintf(lbuf, 100, "Slab: %8" PRIu64 " kB\n", (uint64_t)0);
1223 printme = lbuf;
1224 } else if (startswith(line, "Buffers:")) {
1225 snprintf(lbuf, 100, "Buffers: %8" PRIu64 " kB\n", (uint64_t)0);
1226 printme = lbuf;
1227 } else if (startswith(line, "Cached:")) {
1228 snprintf(lbuf, 100, "Cached: %8" PRIu64 " kB\n",
1229 mstat.total_cache / 1024);
1230 printme = lbuf;
1231 } else if (startswith(line, "SwapCached:")) {
1232 snprintf(lbuf, 100, "SwapCached: %8" PRIu64 " kB\n", (uint64_t)0);
1233 printme = lbuf;
1234 } else if (startswith(line, "Active:")) {
1235 snprintf(lbuf, 100, "Active: %8" PRIu64 " kB\n",
1236 (mstat.total_active_anon +
1237 mstat.total_active_file) /
1238 1024);
1239 printme = lbuf;
1240 } else if (startswith(line, "Inactive:")) {
1241 snprintf(lbuf, 100, "Inactive: %8" PRIu64 " kB\n",
1242 (mstat.total_inactive_anon +
1243 mstat.total_inactive_file) /
1244 1024);
1245 printme = lbuf;
1246 } else if (startswith(line, "Active(anon):")) {
1247 snprintf(lbuf, 100, "Active(anon): %8" PRIu64 " kB\n",
1248 mstat.total_active_anon / 1024);
1249 printme = lbuf;
1250 } else if (startswith(line, "Inactive(anon):")) {
1251 snprintf(lbuf, 100, "Inactive(anon): %8" PRIu64 " kB\n",
1252 mstat.total_inactive_anon / 1024);
1253 printme = lbuf;
1254 } else if (startswith(line, "Active(file):")) {
1255 snprintf(lbuf, 100, "Active(file): %8" PRIu64 " kB\n",
1256 mstat.total_active_file / 1024);
1257 printme = lbuf;
1258 } else if (startswith(line, "Inactive(file):")) {
1259 snprintf(lbuf, 100, "Inactive(file): %8" PRIu64 " kB\n",
1260 mstat.total_inactive_file / 1024);
1261 printme = lbuf;
1262 } else if (startswith(line, "Unevictable:")) {
1263 snprintf(lbuf, 100, "Unevictable: %8" PRIu64 " kB\n",
1264 mstat.total_unevictable / 1024);
1265 printme = lbuf;
1266 } else if (startswith(line, "Dirty:")) {
1267 snprintf(lbuf, 100, "Dirty: %8" PRIu64 " kB\n",
1268 mstat.total_dirty / 1024);
1269 printme = lbuf;
1270 } else if (startswith(line, "Writeback:")) {
1271 snprintf(lbuf, 100, "Writeback: %8" PRIu64 " kB\n",
1272 mstat.total_writeback / 1024);
1273 printme = lbuf;
1274 } else if (startswith(line, "AnonPages:")) {
1275 snprintf(lbuf, 100, "AnonPages: %8" PRIu64 " kB\n",
1276 (mstat.total_active_anon +
1277 mstat.total_inactive_anon - mstat.total_shmem) /
1278 1024);
1279 printme = lbuf;
1280 } else if (startswith(line, "Mapped:")) {
1281 snprintf(lbuf, 100, "Mapped: %8" PRIu64 " kB\n",
1282 mstat.total_mapped_file / 1024);
1283 printme = lbuf;
1284 } else if (startswith(line, "SReclaimable:")) {
1285 snprintf(lbuf, 100, "SReclaimable: %8" PRIu64 " kB\n", (uint64_t)0);
1286 printme = lbuf;
1287 } else if (startswith(line, "SUnreclaim:")) {
1288 snprintf(lbuf, 100, "SUnreclaim: %8" PRIu64 " kB\n", (uint64_t)0);
1289 printme = lbuf;
1290 } else if (startswith(line, "Shmem:")) {
1291 snprintf(lbuf, 100, "Shmem: %8" PRIu64 " kB\n",
1292 mstat.total_shmem / 1024);
1293 printme = lbuf;
1294 } else if (startswith(line, "ShmemHugePages:")) {
1295 snprintf(lbuf, 100, "ShmemHugePages: %8" PRIu64 " kB\n", (uint64_t)0);
1296 printme = lbuf;
1297 } else if (startswith(line, "ShmemPmdMapped:")) {
1298 snprintf(lbuf, 100, "ShmemPmdMapped: %8" PRIu64 " kB\n", (uint64_t)0);
1299 printme = lbuf;
1300 } else if (startswith(line, "AnonHugePages:")) {
1301 snprintf(lbuf, 100, "AnonHugePages: %8" PRIu64 " kB\n",
1302 mstat.total_rss_huge / 1024);
1303 printme = lbuf;
1304 } else {
1305 printme = line;
1306 }
1307
1308 l = snprintf(cache, cache_size, "%s", printme);
1309 if (l < 0)
1310 return log_error(0, "Failed to write cache");
1311 if (l >= cache_size)
1312 return log_error(0, "Write to cache was truncated");
1313
1314 cache += l;
1315 cache_size -= l;
1316 total_len += l;
1317 }
1318
1319 d->cached = 1;
1320 d->size = total_len;
1321 if (total_len > size)
1322 total_len = size;
1323 memcpy(buf, d->buf, total_len);
1324
1325 return total_len;
1326 }
1327
1328 __lxcfs_fuse_ops int proc_read(const char *path, char *buf, size_t size,
1329 off_t offset, struct fuse_file_info *fi)
1330 {
1331 struct file_info *f = INTTYPE_TO_PTR(fi->fh);
1332
1333 switch (f->type) {
1334 case LXC_TYPE_PROC_MEMINFO:
1335 if (liblxcfs_functional())
1336 return proc_meminfo_read(buf, size, offset, fi);
1337
1338 return read_file_fuse_with_offset(LXC_TYPE_PROC_MEMINFO_PATH,
1339 buf, size, offset, f);
1340 case LXC_TYPE_PROC_CPUINFO:
1341 if (liblxcfs_functional())
1342 return proc_cpuinfo_read(buf, size, offset, fi);
1343
1344 return read_file_fuse_with_offset(LXC_TYPE_PROC_CPUINFO_PATH,
1345 buf, size, offset, f);
1346 case LXC_TYPE_PROC_UPTIME:
1347 if (liblxcfs_functional())
1348 return proc_uptime_read(buf, size, offset, fi);
1349
1350 return read_file_fuse_with_offset(LXC_TYPE_PROC_UPTIME_PATH,
1351 buf, size, offset, f);
1352 case LXC_TYPE_PROC_STAT:
1353 if (liblxcfs_functional())
1354 return proc_stat_read(buf, size, offset, fi);
1355
1356 return read_file_fuse_with_offset(LXC_TYPE_PROC_STAT_PATH, buf,
1357 size, offset, f);
1358 case LXC_TYPE_PROC_DISKSTATS:
1359 if (liblxcfs_functional())
1360 return proc_diskstats_read(buf, size, offset, fi);
1361
1362 return read_file_fuse_with_offset(LXC_TYPE_PROC_DISKSTATS_PATH,
1363 buf, size, offset, f);
1364 case LXC_TYPE_PROC_SWAPS:
1365 if (liblxcfs_functional())
1366 return proc_swaps_read(buf, size, offset, fi);
1367
1368 return read_file_fuse_with_offset(LXC_TYPE_PROC_SWAPS_PATH, buf,
1369 size, offset, f);
1370 case LXC_TYPE_PROC_LOADAVG:
1371 if (liblxcfs_functional())
1372 return proc_loadavg_read(buf, size, offset, fi);
1373
1374 return read_file_fuse_with_offset(LXC_TYPE_PROC_LOADAVG_PATH,
1375 buf, size, offset, f);
1376 }
1377
1378 return -EINVAL;
1379 }