]> git.proxmox.com Git - mirror_lxcfs.git/blob - src/proc_fuse.c
proc_fuse: remove unused variable
[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 uint64_t get_memlimit(const char *cgroup, bool swap)
204 {
205 __do_free char *memlimit_str = NULL;
206 uint64_t 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 = strtoull(memlimit_str, &ptr, 10);
216 if (ptr == memlimit_str)
217 memlimit = -1;
218 }
219
220 return memlimit;
221 }
222
223 static uint64_t get_min_memlimit(const char *cgroup, bool swap)
224 {
225 __do_free char *copy = NULL;
226 uint64_t memlimit = 0;
227 uint64_t 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 uint64_t memswlimit = 0, memlimit = 0, memusage = 0, memswusage = 0,
262 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 = strtoull(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 = strtoull(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: %8" PRIu64 " kB", &swap_total);
327 else if (startswith(line, "SwapFree:"))
328 sscanf(line, "SwapFree: %8" PRIu64 " 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%" PRIu64 "\t%" PRIu64 "\t0\n",
335 36, " ", 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, uint64_t *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 uint64_t read = 0, write = 0;
388 uint64_t read_merged = 0, write_merged = 0;
389 uint64_t read_sectors = 0, write_sectors = 0;
390 uint64_t read_ticks = 0, write_ticks = 0;
391 uint64_t ios_pgr = 0, tot_ticks = 0, rq_ticks = 0;
392 uint64_t 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 uint64_t 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", &usage_str))
549 return 0;
550
551 usage = strtoull(usage_str, NULL, 10);
552 return ((double)usage / 1000000000);
553 }
554
555 static uint64_t get_reaper_start_time(pid_t pid)
556 {
557 __do_free void *fopen_cache = NULL;
558 __do_fclose FILE *f = NULL;
559 int ret;
560 uint64_t starttime;
561 /* strlen("/proc/") = 6
562 * +
563 * LXCFS_NUMSTRLEN64
564 * +
565 * strlen("/stat") = 5
566 * +
567 * \0 = 1
568 * */
569 #define __PROC_PID_STAT_LEN (6 + LXCFS_NUMSTRLEN64 + 5 + 1)
570 char path[__PROC_PID_STAT_LEN];
571 pid_t qpid;
572
573 qpid = lookup_initpid_in_store(pid);
574 if (qpid <= 0) {
575 /* Caller can check for EINVAL on 0. */
576 errno = EINVAL;
577 return 0;
578 }
579
580 ret = snprintf(path, __PROC_PID_STAT_LEN, "/proc/%d/stat", qpid);
581 if (ret < 0 || ret >= __PROC_PID_STAT_LEN) {
582 /* Caller can check for EINVAL on 0. */
583 errno = EINVAL;
584 return 0;
585 }
586
587 f = fopen_cached(path, "re", &fopen_cache);
588 if (!f) {
589 /* Caller can check for EINVAL on 0. */
590 errno = EINVAL;
591 return 0;
592 }
593
594 /* Note that the *scanf() argument supression requires that length
595 * modifiers such as "l" are omitted. Otherwise some compilers will yell
596 * at us. It's like telling someone you're not married and then asking
597 * if you can bring your wife to the party.
598 */
599 ret = fscanf(f, "%*d " /* (1) pid %d */
600 "%*s " /* (2) comm %s */
601 "%*c " /* (3) state %c */
602 "%*d " /* (4) ppid %d */
603 "%*d " /* (5) pgrp %d */
604 "%*d " /* (6) session %d */
605 "%*d " /* (7) tty_nr %d */
606 "%*d " /* (8) tpgid %d */
607 "%*u " /* (9) flags %u */
608 "%*u " /* (10) minflt %lu */
609 "%*u " /* (11) cminflt %lu */
610 "%*u " /* (12) majflt %lu */
611 "%*u " /* (13) cmajflt %lu */
612 "%*u " /* (14) utime %lu */
613 "%*u " /* (15) stime %lu */
614 "%*d " /* (16) cutime %ld */
615 "%*d " /* (17) cstime %ld */
616 "%*d " /* (18) priority %ld */
617 "%*d " /* (19) nice %ld */
618 "%*d " /* (20) num_threads %ld */
619 "%*d " /* (21) itrealvalue %ld */
620 "%" PRIu64, /* (22) starttime %llu */
621 &starttime);
622 if (ret != 1)
623 return ret_set_errno(0, EINVAL);
624
625 return ret_set_errno(starttime, 0);
626 }
627
628 static double get_reaper_start_time_in_sec(pid_t pid)
629 {
630 uint64_t clockticks, ticks_per_sec;
631 int64_t ret;
632 double res = 0;
633
634 clockticks = get_reaper_start_time(pid);
635 if (clockticks == 0 && errno == EINVAL)
636 return log_debug(0, "Failed to retrieve start time of pid %d", pid);
637
638 ret = sysconf(_SC_CLK_TCK);
639 if (ret < 0 && errno == EINVAL)
640 return log_debug(0, "Failed to determine number of clock ticks in a second");
641
642 ticks_per_sec = (uint64_t)ret;
643 res = (double)clockticks / ticks_per_sec;
644 return res;
645 }
646
647 static double get_reaper_age(pid_t pid)
648 {
649 uint64_t uptime_ms;
650 double procstart, procage;
651
652 /* We need to substract the time the process has started since system
653 * boot minus the time when the system has started to get the actual
654 * reaper age.
655 */
656 procstart = get_reaper_start_time_in_sec(pid);
657 procage = procstart;
658 if (procstart > 0) {
659 int ret;
660 struct timespec spec;
661
662 ret = clock_gettime(CLOCK_BOOTTIME, &spec);
663 if (ret < 0)
664 return 0;
665
666 /* We could make this more precise here by using the tv_nsec
667 * field in the timespec struct and convert it to milliseconds
668 * and then create a double for the seconds and milliseconds but
669 * that seems more work than it is worth.
670 */
671 uptime_ms = (spec.tv_sec * 1000) + (spec.tv_nsec * 1e-6);
672 procage = (uptime_ms - (procstart * 1000)) / 1000;
673 }
674
675 return procage;
676 }
677
678 /*
679 * We read /proc/uptime and reuse its second field.
680 * For the first field, we use the mtime for the reaper for
681 * the calling pid as returned by getreaperage
682 */
683 static int proc_uptime_read(char *buf, size_t size, off_t offset,
684 struct fuse_file_info *fi)
685 {
686 struct fuse_context *fc = fuse_get_context();
687 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
688 double busytime = get_reaper_busy(fc->pid);
689 char *cache = d->buf;
690 ssize_t total_len = 0;
691 double idletime, reaperage;
692
693 #if RELOADTEST
694 iwashere();
695 #endif
696
697 if (offset) {
698 int left;
699
700 if (!d->cached)
701 return 0;
702
703 if (offset > d->size)
704 return -EINVAL;
705
706 left = d->size - offset;
707 total_len = left > size ? size : left;
708 memcpy(buf, cache + offset, total_len);
709
710 return total_len;
711 }
712
713 reaperage = get_reaper_age(fc->pid);
714 /*
715 * To understand why this is done, please read the comment to the
716 * get_reaper_busy() function.
717 */
718 idletime = reaperage;
719 if (reaperage >= busytime)
720 idletime = reaperage - busytime;
721
722 total_len = snprintf(d->buf, d->buflen, "%.2lf %.2lf\n", reaperage, idletime);
723 if (total_len < 0 || total_len >= d->buflen)
724 return log_error(0, "Failed to write to cache");
725
726 d->size = (int)total_len;
727 d->cached = 1;
728
729 if (total_len > size)
730 total_len = size;
731
732 memcpy(buf, d->buf, total_len);
733 return total_len;
734 }
735
736 #define CPUALL_MAX_SIZE (BUF_RESERVE_SIZE / 2)
737 static int proc_stat_read(char *buf, size_t size, off_t offset,
738 struct fuse_file_info *fi)
739 {
740 __do_free char *cg = NULL, *cpuset = NULL, *line = NULL;
741 __do_free void *fopen_cache = NULL;
742 __do_free struct cpuacct_usage *cg_cpu_usage = NULL;
743 __do_fclose FILE *f = NULL;
744 struct fuse_context *fc = fuse_get_context();
745 struct lxcfs_opts *opts = (struct lxcfs_opts *)fc->private_data;
746 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
747 size_t linelen = 0, total_len = 0;
748 int curcpu = -1; /* cpu numbering starts at 0 */
749 int physcpu = 0;
750 uint64_t user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0,
751 softirq = 0, steal = 0, guest = 0, guest_nice = 0;
752 uint64_t user_sum = 0, nice_sum = 0, system_sum = 0, idle_sum = 0,
753 iowait_sum = 0, irq_sum = 0, softirq_sum = 0, steal_sum = 0,
754 guest_sum = 0, guest_nice_sum = 0;
755 char cpuall[CPUALL_MAX_SIZE];
756 /* reserve for cpu all */
757 char *cache = d->buf + CPUALL_MAX_SIZE;
758 size_t cache_size = d->buflen - CPUALL_MAX_SIZE;
759 int cg_cpu_usage_size = 0;
760
761 if (offset) {
762 int left;
763
764 if (offset > d->size)
765 return -EINVAL;
766
767 if (!d->cached)
768 return 0;
769
770 left = d->size - offset;
771 total_len = left > size ? size : left;
772 memcpy(buf, d->buf + offset, total_len);
773
774 return total_len;
775 }
776
777 pid_t initpid = lookup_initpid_in_store(fc->pid);
778 if (initpid <= 1 || is_shared_pidns(initpid))
779 initpid = fc->pid;
780
781 /*
782 * when container run with host pid namespace initpid == 1, cgroup will "/"
783 * we should return host os's /proc contents.
784 * in some case cpuacct_usage.all in "/" will larger then /proc/stat
785 */
786 if (initpid == 1)
787 return read_file_fuse("/proc/stat", buf, size, d);
788
789 cg = get_pid_cgroup(initpid, "cpuset");
790 if (!cg)
791 return read_file_fuse("/proc/stat", buf, size, d);
792 prune_init_slice(cg);
793
794 cpuset = get_cpuset(cg);
795 if (!cpuset)
796 return 0;
797
798 f = fopen_cached("/proc/stat", "re", &fopen_cache);
799 if (!f)
800 return 0;
801
802 /*
803 * Read cpuacct.usage_all for all CPUs.
804 * If the cpuacct cgroup is present, it is used to calculate the container's
805 * CPU usage. If not, values from the host's /proc/stat are used.
806 */
807 if (read_cpuacct_usage_all(cg, cpuset, &cg_cpu_usage, &cg_cpu_usage_size) == 0) {
808 if (cgroup_ops->can_use_cpuview(cgroup_ops) && opts && opts->use_cfs) {
809 total_len = cpuview_proc_stat(cg, cpuset, cg_cpu_usage,
810 cg_cpu_usage_size, f,
811 d->buf, d->buflen);
812 goto out;
813 }
814 } else {
815 lxcfs_v("proc_stat_read failed to read from cpuacct, falling back to the host's /proc/stat");
816 }
817
818 //skip first line
819 if (getline(&line, &linelen, f) < 0)
820 return log_error(0, "proc_stat_read read first line failed");
821
822 while (getline(&line, &linelen, f) != -1) {
823 ssize_t l;
824 char cpu_char[10]; /* That's a lot of cores */
825 char *c;
826 uint64_t all_used, cg_used, new_idle;
827 int ret;
828
829 if (strlen(line) == 0)
830 continue;
831 if (sscanf(line, "cpu%9[^ ]", cpu_char) != 1) {
832 /* not a ^cpuN line containing a number N, just print it */
833 l = snprintf(cache, cache_size, "%s", line);
834 if (l < 0)
835 return log_error(0, "Failed to write cache");
836 if (l >= cache_size)
837 return log_error(0, "Write to cache was truncated");
838
839 cache += l;
840 cache_size -= l;
841 total_len += l;
842
843 continue;
844 }
845
846 if (sscanf(cpu_char, "%d", &physcpu) != 1)
847 continue;
848
849 if (!cpu_in_cpuset(physcpu, cpuset))
850 continue;
851
852 curcpu++;
853
854 ret = sscanf(line, "%*s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
855 &user,
856 &nice,
857 &system,
858 &idle,
859 &iowait,
860 &irq,
861 &softirq,
862 &steal,
863 &guest,
864 &guest_nice);
865 if (ret != 10 || !cg_cpu_usage) {
866 c = strchr(line, ' ');
867 if (!c)
868 continue;
869
870 l = snprintf(cache, cache_size, "cpu%d%s", curcpu, c);
871 if (l < 0)
872 return log_error(0, "Failed to write cache");
873 if (l >= cache_size)
874 return log_error(0, "Write to cache was truncated");
875
876 cache += l;
877 cache_size -= l;
878 total_len += l;
879
880 if (ret != 10)
881 continue;
882 }
883
884 if (cg_cpu_usage) {
885 if (physcpu >= cg_cpu_usage_size)
886 break;
887
888 all_used = user + nice + system + iowait + irq + softirq + steal + guest + guest_nice;
889 cg_used = cg_cpu_usage[physcpu].user + cg_cpu_usage[physcpu].system;
890
891 if (all_used >= cg_used) {
892 new_idle = idle + (all_used - cg_used);
893
894 } else {
895 lxcfs_error("cpu%d from %s has unexpected cpu time: %" PRIu64 " in /proc/stat, %" PRIu64 " in cpuacct.usage_all; unable to determine idle time",
896 curcpu, cg, all_used, cg_used);
897 new_idle = idle;
898 }
899
900 l = snprintf(cache, cache_size,
901 "cpu%d %" PRIu64 " 0 %" PRIu64 " %" PRIu64 " 0 0 0 0 0 0\n",
902 curcpu, cg_cpu_usage[physcpu].user,
903 cg_cpu_usage[physcpu].system, new_idle);
904 if (l < 0)
905 return log_error(0, "Failed to write cache");
906 if (l >= cache_size)
907 return log_error(0, "Write to cache was truncated");
908
909 cache += l;
910 cache_size -= l;
911 total_len += l;
912
913 user_sum += cg_cpu_usage[physcpu].user;
914 system_sum += cg_cpu_usage[physcpu].system;
915 idle_sum += new_idle;
916 } else {
917 user_sum += user;
918 nice_sum += nice;
919 system_sum += system;
920 idle_sum += idle;
921 iowait_sum += iowait;
922 irq_sum += irq;
923 softirq_sum += softirq;
924 steal_sum += steal;
925 guest_sum += guest;
926 guest_nice_sum += guest_nice;
927 }
928 }
929
930 cache = d->buf;
931
932 int cpuall_len = snprintf(cpuall, CPUALL_MAX_SIZE, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
933 user_sum,
934 nice_sum,
935 system_sum,
936 idle_sum,
937 iowait_sum,
938 irq_sum,
939 softirq_sum,
940 steal_sum,
941 guest_sum,
942 guest_nice_sum);
943 if (cpuall_len > 0 && cpuall_len < CPUALL_MAX_SIZE) {
944 memcpy(cache, cpuall, cpuall_len);
945 cache += cpuall_len;
946 } else {
947 /* shouldn't happen */
948 lxcfs_error("proc_stat_read copy cpuall failed, cpuall_len=%d", cpuall_len);
949 cpuall_len = 0;
950 }
951
952 memmove(cache, d->buf + CPUALL_MAX_SIZE, total_len);
953 total_len += cpuall_len;
954
955 out:
956 d->cached = 1;
957 d->size = total_len;
958 if (total_len > size)
959 total_len = size;
960
961 memcpy(buf, d->buf, total_len);
962 return total_len;
963 }
964
965 /* Note that "memory.stat" in cgroup2 is hierarchical by default. */
966 static bool cgroup_parse_memory_stat(const char *cgroup, struct memory_stat *mstat)
967 {
968 __do_close_prot_errno int fd = -EBADF;
969 __do_fclose FILE *f = NULL;
970 __do_free char *line = NULL;
971 __do_free void *fdopen_cache = NULL;
972 bool unified;
973 size_t len = 0;
974 ssize_t linelen;
975
976 fd = cgroup_ops->get_memory_stats_fd(cgroup_ops, cgroup);
977 if (fd < 0)
978 return false;
979
980 f = fdopen_cached(fd, "re", &fdopen_cache);
981 if (!f)
982 return false;
983
984 unified = pure_unified_layout(cgroup_ops);
985 while ((linelen = getline(&line, &len, f)) != -1) {
986 if (!unified && startswith(line, "hierarchical_memory_limit")) {
987 sscanf(line, "hierarchical_memory_limit %" PRIu64, &(mstat->hierarchical_memory_limit));
988 } else if (!unified && startswith(line, "hierarchical_memsw_limit")) {
989 sscanf(line, "hierarchical_memsw_limit %" PRIu64, &(mstat->hierarchical_memsw_limit));
990 } else if (startswith(line, unified ? "file" :"total_cache")) {
991 sscanf(line, unified ? "file %" PRIu64 : "total_cache %" PRIu64, &(mstat->total_cache));
992 } else if (!unified && startswith(line, "total_rss")) {
993 sscanf(line, "total_rss %" PRIu64, &(mstat->total_rss));
994 } else if (!unified && startswith(line, "total_rss_huge")) {
995 sscanf(line, "total_rss_huge %" PRIu64, &(mstat->total_rss_huge));
996 } else if (startswith(line, unified ? "shmem" : "total_shmem")) {
997 sscanf(line, unified ? "shmem %" PRIu64 : "total_shmem %" PRIu64, &(mstat->total_shmem));
998 } else if (startswith(line, unified ? "file_mapped" : "total_mapped_file")) {
999 sscanf(line, unified ? "file_mapped %" PRIu64 : "total_mapped_file %" PRIu64, &(mstat->total_mapped_file));
1000 } else if (!unified && startswith(line, "total_dirty")) {
1001 sscanf(line, "total_dirty %" PRIu64, &(mstat->total_dirty));
1002 } else if (!unified && startswith(line, "total_writeback")) {
1003 sscanf(line, "total_writeback %" PRIu64, &(mstat->total_writeback));
1004 } else if (!unified && startswith(line, "total_swap")) {
1005 sscanf(line, "total_swap %" PRIu64, &(mstat->total_swap));
1006 } else if (!unified && startswith(line, "total_pgpgin")) {
1007 sscanf(line, "total_pgpgin %" PRIu64, &(mstat->total_pgpgin));
1008 } else if (!unified && startswith(line, "total_pgpgout")) {
1009 sscanf(line, "total_pgpgout %" PRIu64, &(mstat->total_pgpgout));
1010 } else if (startswith(line, unified ? "pgfault" : "total_pgfault")) {
1011 sscanf(line, unified ? "pgfault %" PRIu64 : "total_pgfault %" PRIu64, &(mstat->total_pgfault));
1012 } else if (startswith(line, unified ? "pgmajfault" : "total_pgmajfault")) {
1013 sscanf(line, unified ? "pgmajfault %" PRIu64 : "total_pgmajfault %" PRIu64, &(mstat->total_pgmajfault));
1014 } else if (startswith(line, unified ? "inactive_anon" : "total_inactive_anon")) {
1015 sscanf(line, unified ? "inactive_anon %" PRIu64 : "total_inactive_anon %" PRIu64, &(mstat->total_inactive_anon));
1016 } else if (startswith(line, unified ? "active_anon" : "total_active_anon")) {
1017 sscanf(line, unified ? "active_anon %" PRIu64 : "total_active_anon %" PRIu64, &(mstat->total_active_anon));
1018 } else if (startswith(line, unified ? "inactive_file" : "total_inactive_file")) {
1019 sscanf(line, unified ? "inactive_file %" PRIu64 : "total_inactive_file %" PRIu64, &(mstat->total_inactive_file));
1020 } else if (startswith(line, unified ? "active_file" : "total_active_file")) {
1021 sscanf(line, unified ? "active_file %" PRIu64 : "total_active_file %" PRIu64, &(mstat->total_active_file));
1022 } else if (startswith(line, unified ? "unevictable" : "total_unevictable")) {
1023 sscanf(line, unified ? "unevictable %" PRIu64 : "total_unevictable %" PRIu64, &(mstat->total_unevictable));
1024 }
1025 }
1026
1027 return true;
1028 }
1029
1030 static int proc_meminfo_read(char *buf, size_t size, off_t offset,
1031 struct fuse_file_info *fi)
1032 {
1033 __do_free char *cgroup = NULL, *line = NULL, *memusage_str = NULL,
1034 *memswlimit_str = NULL, *memswusage_str = NULL;
1035 __do_free void *fopen_cache = NULL;
1036 __do_fclose FILE *f = NULL;
1037 struct fuse_context *fc = fuse_get_context();
1038 struct lxcfs_opts *opts = (struct lxcfs_opts *)fuse_get_context()->private_data;
1039 struct file_info *d = INTTYPE_TO_PTR(fi->fh);
1040 uint64_t memlimit = 0, memusage = 0, memswlimit = 0, memswusage = 0,
1041 hosttotal = 0;
1042 struct memory_stat mstat = {};
1043 size_t linelen = 0, total_len = 0;
1044 char *cache = d->buf;
1045 size_t cache_size = d->buflen;
1046 int ret;
1047
1048 if (offset) {
1049 int left;
1050
1051 if (offset > d->size)
1052 return -EINVAL;
1053
1054 if (!d->cached)
1055 return 0;
1056
1057 left = d->size - offset;
1058 total_len = left > size ? size : left;
1059 memcpy(buf, cache + offset, total_len);
1060
1061 return total_len;
1062 }
1063
1064 pid_t initpid = lookup_initpid_in_store(fc->pid);
1065 if (initpid <= 1 || is_shared_pidns(initpid))
1066 initpid = fc->pid;
1067
1068 cgroup = get_pid_cgroup(initpid, "memory");
1069 if (!cgroup)
1070 return read_file_fuse("/proc/meminfo", buf, size, d);
1071
1072 prune_init_slice(cgroup);
1073
1074 memlimit = get_min_memlimit(cgroup, false);
1075
1076 ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
1077 if (ret < 0)
1078 return 0;
1079
1080 if (!cgroup_parse_memory_stat(cgroup, &mstat))
1081 return 0;
1082
1083 /*
1084 * Following values are allowed to fail, because swapaccount might be
1085 * turned off for current kernel.
1086 */
1087 ret = cgroup_ops->get_memory_swap_max(cgroup_ops, cgroup, &memswlimit_str);
1088 if (ret >= 0)
1089 ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
1090 if (ret >= 0) {
1091 memswlimit = get_min_memlimit(cgroup, true);
1092 memswusage = strtoull(memswusage_str, NULL, 10);
1093 memswlimit = memswlimit / 1024;
1094 memswusage = memswusage / 1024;
1095 }
1096
1097 memusage = strtoull(memusage_str, NULL, 10);
1098 memlimit /= 1024;
1099 memusage /= 1024;
1100
1101 f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
1102 if (!f)
1103 return 0;
1104
1105 while (getline(&line, &linelen, f) != -1) {
1106 ssize_t l;
1107 char *printme, lbuf[100];
1108
1109 memset(lbuf, 0, 100);
1110 if (startswith(line, "MemTotal:")) {
1111 sscanf(line+sizeof("MemTotal:")-1, "%" PRIu64, &hosttotal);
1112 if (hosttotal < memlimit)
1113 memlimit = hosttotal;
1114 snprintf(lbuf, 100, "MemTotal: %8" PRIu64 " kB\n", memlimit);
1115 printme = lbuf;
1116 } else if (startswith(line, "MemFree:")) {
1117 snprintf(lbuf, 100, "MemFree: %8" PRIu64 " kB\n", memlimit - memusage);
1118 printme = lbuf;
1119 } else if (startswith(line, "MemAvailable:")) {
1120 snprintf(lbuf, 100, "MemAvailable: %8" PRIu64 " kB\n", memlimit - memusage + mstat.total_cache / 1024);
1121 printme = lbuf;
1122 } else if (startswith(line, "SwapTotal:") && memswlimit > 0 &&
1123 opts && opts->swap_off == false) {
1124 memswlimit -= memlimit;
1125 snprintf(lbuf, 100, "SwapTotal: %8" PRIu64 " kB\n", memswlimit);
1126 printme = lbuf;
1127 } else if (startswith(line, "SwapTotal:") && opts && opts->swap_off == true) {
1128 snprintf(lbuf, 100, "SwapTotal: %8" PRIu64 " kB\n", (uint64_t)0);
1129 printme = lbuf;
1130 } else if (startswith(line, "SwapFree:") && memswlimit > 0 &&
1131 memswusage > 0 && opts && opts->swap_off == false) {
1132 uint64_t swaptotal = memswlimit,
1133 swapusage = memusage > memswusage
1134 ? 0
1135 : memswusage - memusage,
1136 swapfree = swapusage < swaptotal
1137 ? swaptotal - swapusage
1138 : 0;
1139 snprintf(lbuf, 100, "SwapFree: %8" PRIu64 " kB\n", swapfree);
1140 printme = lbuf;
1141 } else if (startswith(line, "SwapFree:") && opts && opts->swap_off == true) {
1142 snprintf(lbuf, 100, "SwapFree: %8" PRIu64 " kB\n", (uint64_t)0);
1143 printme = lbuf;
1144 } else if (startswith(line, "Slab:")) {
1145 snprintf(lbuf, 100, "Slab: %8" PRIu64 " kB\n", (uint64_t)0);
1146 printme = lbuf;
1147 } else if (startswith(line, "Buffers:")) {
1148 snprintf(lbuf, 100, "Buffers: %8" PRIu64 " kB\n", (uint64_t)0);
1149 printme = lbuf;
1150 } else if (startswith(line, "Cached:")) {
1151 snprintf(lbuf, 100, "Cached: %8" PRIu64 " kB\n",
1152 mstat.total_cache / 1024);
1153 printme = lbuf;
1154 } else if (startswith(line, "SwapCached:")) {
1155 snprintf(lbuf, 100, "SwapCached: %8" PRIu64 " kB\n", (uint64_t)0);
1156 printme = lbuf;
1157 } else if (startswith(line, "Active:")) {
1158 snprintf(lbuf, 100, "Active: %8" PRIu64 " kB\n",
1159 (mstat.total_active_anon +
1160 mstat.total_active_file) /
1161 1024);
1162 printme = lbuf;
1163 } else if (startswith(line, "Inactive:")) {
1164 snprintf(lbuf, 100, "Inactive: %8" PRIu64 " kB\n",
1165 (mstat.total_inactive_anon +
1166 mstat.total_inactive_file) /
1167 1024);
1168 printme = lbuf;
1169 } else if (startswith(line, "Active(anon)")) {
1170 snprintf(lbuf, 100, "Active(anon): %8" PRIu64 " kB\n",
1171 mstat.total_active_anon / 1024);
1172 printme = lbuf;
1173 } else if (startswith(line, "Inactive(anon)")) {
1174 snprintf(lbuf, 100, "Inactive(anon): %8" PRIu64 " kB\n",
1175 mstat.total_inactive_anon / 1024);
1176 printme = lbuf;
1177 } else if (startswith(line, "Active(file)")) {
1178 snprintf(lbuf, 100, "Active(file): %8" PRIu64 " kB\n",
1179 mstat.total_active_file / 1024);
1180 printme = lbuf;
1181 } else if (startswith(line, "Inactive(file)")) {
1182 snprintf(lbuf, 100, "Inactive(file): %8" PRIu64 " kB\n",
1183 mstat.total_inactive_file / 1024);
1184 printme = lbuf;
1185 } else if (startswith(line, "Unevictable")) {
1186 snprintf(lbuf, 100, "Unevictable: %8" PRIu64 " kB\n",
1187 mstat.total_unevictable / 1024);
1188 printme = lbuf;
1189 } else if (startswith(line, "Dirty")) {
1190 snprintf(lbuf, 100, "Dirty: %8" PRIu64 " kB\n",
1191 mstat.total_dirty / 1024);
1192 printme = lbuf;
1193 } else if (startswith(line, "Writeback")) {
1194 snprintf(lbuf, 100, "Writeback: %8" PRIu64 " kB\n",
1195 mstat.total_writeback / 1024);
1196 printme = lbuf;
1197 } else if (startswith(line, "AnonPages")) {
1198 snprintf(lbuf, 100, "AnonPages: %8" PRIu64 " kB\n",
1199 (mstat.total_active_anon +
1200 mstat.total_inactive_anon - mstat.total_shmem) /
1201 1024);
1202 printme = lbuf;
1203 } else if (startswith(line, "Mapped")) {
1204 snprintf(lbuf, 100, "Mapped: %8" PRIu64 " kB\n",
1205 mstat.total_mapped_file / 1024);
1206 printme = lbuf;
1207 } else if (startswith(line, "SReclaimable")) {
1208 snprintf(lbuf, 100, "SReclaimable: %8" PRIu64 " kB\n", (uint64_t)0);
1209 printme = lbuf;
1210 } else if (startswith(line, "SUnreclaim")) {
1211 snprintf(lbuf, 100, "SUnreclaim: %8" PRIu64 " kB\n", (uint64_t)0);
1212 printme = lbuf;
1213 } else if (startswith(line, "Shmem:")) {
1214 snprintf(lbuf, 100, "Shmem: %8" PRIu64 " kB\n",
1215 mstat.total_shmem / 1024);
1216 printme = lbuf;
1217 } else if (startswith(line, "ShmemHugePages")) {
1218 snprintf(lbuf, 100, "ShmemHugePages: %8" PRIu64 " kB\n", (uint64_t)0);
1219 printme = lbuf;
1220 } else if (startswith(line, "ShmemPmdMapped")) {
1221 snprintf(lbuf, 100, "ShmemPmdMapped: %8" PRIu64 " kB\n", (uint64_t)0);
1222 printme = lbuf;
1223 } else if (startswith(line, "AnonHugePages")) {
1224 snprintf(lbuf, 100, "AnonHugePages: %8" PRIu64 " kB\n",
1225 mstat.total_rss_huge / 1024);
1226 printme = lbuf;
1227 } else {
1228 printme = line;
1229 }
1230
1231 l = snprintf(cache, cache_size, "%s", printme);
1232 if (l < 0)
1233 return log_error(0, "Failed to write cache");
1234 if (l >= cache_size)
1235 return log_error(0, "Write to cache was truncated");
1236
1237 cache += l;
1238 cache_size -= l;
1239 total_len += l;
1240 }
1241
1242 d->cached = 1;
1243 d->size = total_len;
1244 if (total_len > size)
1245 total_len = size;
1246 memcpy(buf, d->buf, total_len);
1247
1248 return total_len;
1249 }
1250
1251 __lxcfs_fuse_ops int proc_read(const char *path, char *buf, size_t size,
1252 off_t offset, struct fuse_file_info *fi)
1253 {
1254 struct file_info *f = INTTYPE_TO_PTR(fi->fh);
1255
1256 switch (f->type) {
1257 case LXC_TYPE_PROC_MEMINFO:
1258 if (liblxcfs_functional())
1259 return proc_meminfo_read(buf, size, offset, fi);
1260
1261 return read_file_fuse_with_offset(LXC_TYPE_PROC_MEMINFO_PATH,
1262 buf, size, offset, f);
1263 case LXC_TYPE_PROC_CPUINFO:
1264 if (liblxcfs_functional())
1265 return proc_cpuinfo_read(buf, size, offset, fi);
1266
1267 return read_file_fuse_with_offset(LXC_TYPE_PROC_CPUINFO_PATH,
1268 buf, size, offset, f);
1269 case LXC_TYPE_PROC_UPTIME:
1270 if (liblxcfs_functional())
1271 return proc_uptime_read(buf, size, offset, fi);
1272
1273 return read_file_fuse_with_offset(LXC_TYPE_PROC_UPTIME_PATH,
1274 buf, size, offset, f);
1275 case LXC_TYPE_PROC_STAT:
1276 if (liblxcfs_functional())
1277 return proc_stat_read(buf, size, offset, fi);
1278
1279 return read_file_fuse_with_offset(LXC_TYPE_PROC_STAT_PATH, buf,
1280 size, offset, f);
1281 case LXC_TYPE_PROC_DISKSTATS:
1282 if (liblxcfs_functional())
1283 return proc_diskstats_read(buf, size, offset, fi);
1284
1285 return read_file_fuse_with_offset(LXC_TYPE_PROC_DISKSTATS_PATH,
1286 buf, size, offset, f);
1287 case LXC_TYPE_PROC_SWAPS:
1288 if (liblxcfs_functional())
1289 return proc_swaps_read(buf, size, offset, fi);
1290
1291 return read_file_fuse_with_offset(LXC_TYPE_PROC_SWAPS_PATH, buf,
1292 size, offset, f);
1293 case LXC_TYPE_PROC_LOADAVG:
1294 if (liblxcfs_functional())
1295 return proc_loadavg_read(buf, size, offset, fi);
1296
1297 return read_file_fuse_with_offset(LXC_TYPE_PROC_LOADAVG_PATH,
1298 buf, size, offset, f);
1299 }
1300
1301 return -EINVAL;
1302 }