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