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