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