/* * Copyright (C) 2014 Red Hat, Inc. * Copyright (C) 2012 Fujitsu Limited. * * lxc_fuse.c: fuse filesystem support for libvirt lxc * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include #include #include #include #if WITH_FUSE # if WITH_FUSE == 3 # define FUSE_USE_VERSION 31 # else # define FUSE_USE_VERSION 26 # endif # include # if FUSE_USE_VERSION >= 31 # include # endif #endif #include "lxc_fuse.h" #include "lxc_cgroup.h" #include "lxc_conf.h" #include "virerror.h" #include "virfile.h" #include "virbuffer.h" #include "virutil.h" #define VIR_FROM_THIS VIR_FROM_LXC #if WITH_FUSE struct virLXCFuse { virDomainDef *def; virThread thread; char *mountpoint; struct fuse *fuse; # if FUSE_USE_VERSION < 31 struct fuse_chan *ch; # else struct fuse_session *sess; # endif virMutex lock; }; static const char *fuse_meminfo_path = "/meminfo"; static int lxcProcGetattrImpl(const char *path, struct stat *stbuf) { g_autofree char *mempath = NULL; struct stat sb; struct fuse_context *context = fuse_get_context(); virDomainDef *def = (virDomainDef *)context->private_data; memset(stbuf, 0, sizeof(struct stat)); mempath = g_strdup_printf("/proc/%s", path); if (STREQ(path, "/")) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (STREQ(path, fuse_meminfo_path)) { if (stat(mempath, &sb) < 0) return -errno; stbuf->st_uid = def->idmap.uidmap ? def->idmap.uidmap[0].target : 0; stbuf->st_gid = def->idmap.gidmap ? def->idmap.gidmap[0].target : 0; stbuf->st_mode = sb.st_mode; stbuf->st_nlink = 1; stbuf->st_blksize = sb.st_blksize; stbuf->st_blocks = sb.st_blocks; stbuf->st_size = sb.st_size; stbuf->st_atime = sb.st_atime; stbuf->st_ctime = sb.st_ctime; stbuf->st_mtime = sb.st_mtime; } else { return -ENOENT; } return 0; } # if FUSE_USE_VERSION >= 31 static int lxcProcGetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi G_GNUC_UNUSED) { return lxcProcGetattrImpl(path, stbuf); } # else static int lxcProcGetattr(const char *path, struct stat *stbuf) { return lxcProcGetattrImpl(path, stbuf); } # endif # if FUSE_USE_VERSION >= 31 static int lxcProcReaddir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset G_GNUC_UNUSED, struct fuse_file_info *fi G_GNUC_UNUSED, enum fuse_readdir_flags unused_flags G_GNUC_UNUSED) { if (STRNEQ(path, "/")) return -ENOENT; filler(buf, ".", NULL, 0, 0); filler(buf, "..", NULL, 0, 0); filler(buf, fuse_meminfo_path + 1, NULL, 0, 0); return 0; } # else static int lxcProcReaddir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset G_GNUC_UNUSED, struct fuse_file_info *fi G_GNUC_UNUSED) { if (STRNEQ(path, "/")) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, fuse_meminfo_path + 1, NULL, 0); return 0; } # endif static int lxcProcOpen(const char *path, struct fuse_file_info *fi) { if (STRNEQ(path, fuse_meminfo_path)) return -ENOENT; if ((fi->flags & O_ACCMODE) != O_RDONLY) return -EACCES; fi->direct_io = 1; fi->nonseekable = 1; return 0; } static int lxcProcHostRead(char *path, char *buf, size_t size, off_t offset) { VIR_AUTOCLOSE fd = -1; int res; fd = open(path, O_RDONLY); if (fd == -1) return -errno; if ((res = pread(fd, buf, size, offset)) < 0) res = -errno; return res; } static int lxcProcReadMeminfo(char *hostpath, virDomainDef *def, char *buf, size_t size, off_t offset) { int res; g_autoptr(FILE) fp = NULL; g_autofree char *line = NULL; size_t n; struct virLXCMeminfo meminfo; g_auto(virBuffer) buffer = VIR_BUFFER_INITIALIZER; const char *new_meminfo = NULL; if (virLXCCgroupGetMeminfo(&meminfo) < 0) { virErrorSetErrnoFromLastError(); return -errno; } fp = fopen(hostpath, "r"); if (fp == NULL) { virReportSystemError(errno, _("Cannot open %1$s"), hostpath); return -errno; } res = -1; while (getline(&line, &n, fp) > 0) { char *ptr = strchr(line, ':'); if (!ptr) continue; *ptr = '\0'; if (STREQ(line, "MemTotal") && (virMemoryLimitIsSet(def->mem.hard_limit) || virDomainDefGetMemoryTotal(def))) { virBufferAsprintf(&buffer, "MemTotal: %8llu kB\n", meminfo.memtotal); } else if (STREQ(line, "MemFree") && (virMemoryLimitIsSet(def->mem.hard_limit) || virDomainDefGetMemoryTotal(def))) { virBufferAsprintf(&buffer, "MemFree: %8llu kB\n", (meminfo.memtotal - meminfo.memusage)); } else if (STREQ(line, "MemAvailable") && (virMemoryLimitIsSet(def->mem.hard_limit) || virDomainDefGetMemoryTotal(def))) { /* MemAvailable is actually MemFree + SRReclaimable + some other bits, but MemFree is the closest approximation we have */ virBufferAsprintf(&buffer, "MemAvailable: %8llu kB\n", (meminfo.memtotal - meminfo.memusage)); } else if (STREQ(line, "Buffers")) { virBufferAsprintf(&buffer, "Buffers: %8d kB\n", 0); } else if (STREQ(line, "Cached")) { virBufferAsprintf(&buffer, "Cached: %8llu kB\n", meminfo.cached); } else if (STREQ(line, "Active")) { virBufferAsprintf(&buffer, "Active: %8llu kB\n", (meminfo.active_anon + meminfo.active_file)); } else if (STREQ(line, "Inactive")) { virBufferAsprintf(&buffer, "Inactive: %8llu kB\n", (meminfo.inactive_anon + meminfo.inactive_file)); } else if (STREQ(line, "Active(anon)")) { virBufferAsprintf(&buffer, "Active(anon): %8llu kB\n", meminfo.active_anon); } else if (STREQ(line, "Inactive(anon)")) { virBufferAsprintf(&buffer, "Inactive(anon): %8llu kB\n", meminfo.inactive_anon); } else if (STREQ(line, "Active(file)")) { virBufferAsprintf(&buffer, "Active(file): %8llu kB\n", meminfo.active_file); } else if (STREQ(line, "Inactive(file)")) { virBufferAsprintf(&buffer, "Inactive(file): %8llu kB\n", meminfo.inactive_file); } else if (STREQ(line, "Unevictable")) { virBufferAsprintf(&buffer, "Unevictable: %8llu kB\n", meminfo.unevictable); } else if (STREQ(line, "SwapTotal") && virMemoryLimitIsSet(def->mem.swap_hard_limit)) { virBufferAsprintf(&buffer, "SwapTotal: %8llu kB\n", (meminfo.swaptotal - meminfo.memtotal)); } else if (STREQ(line, "SwapFree") && virMemoryLimitIsSet(def->mem.swap_hard_limit)) { virBufferAsprintf(&buffer, "SwapFree: %8llu kB\n", (meminfo.swaptotal - meminfo.memtotal - meminfo.swapusage + meminfo.memusage)); } else if (STREQ(line, "Slab")) { virBufferAsprintf(&buffer, "Slab: %8d kB\n", 0); } else if (STREQ(line, "SReclaimable")) { virBufferAsprintf(&buffer, "SReclaimable: %8d kB\n", 0); } else if (STREQ(line, "SUnreclaim")) { virBufferAsprintf(&buffer, "SUnreclaim: %8d kB\n", 0); } else { *ptr = ':'; virBufferAdd(&buffer, line, -1); } } new_meminfo = virBufferCurrentContent(&buffer); res = virBufferUse(&buffer); if (offset > res) return 0; res -= offset; if (res > size) res = size; memcpy(buf, new_meminfo + offset, res); return res; } static int lxcProcRead(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi G_GNUC_UNUSED) { int res = -ENOENT; g_autofree char *hostpath = NULL; struct fuse_context *context = NULL; virDomainDef *def = NULL; hostpath = g_strdup_printf("/proc/%s", path); context = fuse_get_context(); def = (virDomainDef *)context->private_data; if (STREQ(path, fuse_meminfo_path)) { if ((res = lxcProcReadMeminfo(hostpath, def, buf, size, offset)) < 0) res = lxcProcHostRead(hostpath, buf, size, offset); } return res; } static struct fuse_operations lxcProcOper = { .getattr = lxcProcGetattr, .readdir = lxcProcReaddir, .open = lxcProcOpen, .read = lxcProcRead, }; static void lxcFuseDestroy(struct virLXCFuse *fuse) { VIR_LOCK_GUARD lock = virLockGuardLock(&fuse->lock); # if FUSE_USE_VERSION >= 31 fuse_unmount(fuse->fuse); # else fuse_unmount(fuse->mountpoint, fuse->ch); # endif g_clear_pointer(&fuse->fuse, fuse_destroy); } static void lxcFuseRun(void *opaque) { struct virLXCFuse *fuse = opaque; if (fuse_loop(fuse->fuse) < 0) virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("fuse_loop failed")); lxcFuseDestroy(fuse); } int lxcSetupFuse(struct virLXCFuse **f, virDomainDef *def) { int ret = -1; struct fuse_args args = FUSE_ARGS_INIT(0, NULL); g_autofree struct virLXCFuse *fuse = g_new0(virLXCFuse, 1); fuse->def = def; if (virMutexInit(&fuse->lock) < 0) return -1; fuse->mountpoint = g_strdup_printf("%s/%s.fuse/", LXC_STATE_DIR, def->name); if (g_mkdir_with_parents(fuse->mountpoint, 0777) < 0) { virReportSystemError(errno, _("Cannot create %1$s"), fuse->mountpoint); goto error; } /* process name is libvirt_lxc */ if (fuse_opt_add_arg(&args, "libvirt_lxc") == -1 || fuse_opt_add_arg(&args, "-oallow_other") == -1 || fuse_opt_add_arg(&args, "-ofsname=libvirt") == -1) goto error; # if FUSE_USE_VERSION >= 31 fuse->fuse = fuse_new(&args, &lxcProcOper, sizeof(lxcProcOper), fuse->def); if (fuse->fuse == NULL) goto error; if (fuse_mount(fuse->fuse, fuse->mountpoint) < 0) goto error; fuse->sess = fuse_get_session(fuse->fuse); if (virSetInherit(fuse_session_fd(fuse->sess), false) < 0) { virReportSystemError(errno, "%s", _("Cannot disable close-on-exec flag")); goto error; } # else fuse->ch = fuse_mount(fuse->mountpoint, &args); if (fuse->ch == NULL) goto error; fuse->fuse = fuse_new(fuse->ch, &args, &lxcProcOper, sizeof(lxcProcOper), fuse->def); if (fuse->fuse == NULL) { goto error; } # endif *f = g_steal_pointer(&fuse); ret = 0; cleanup: fuse_opt_free_args(&args); return ret; error: # if FUSE_USE_VERSION < 31 if (fuse->ch) fuse_unmount(fuse->mountpoint, fuse->ch); # else fuse_unmount(fuse->fuse); fuse_destroy(fuse->fuse); # endif g_free(fuse->mountpoint); virMutexDestroy(&fuse->lock); goto cleanup; } int lxcStartFuse(struct virLXCFuse *fuse) { if (virThreadCreateFull(&fuse->thread, false, lxcFuseRun, "lxc-fuse", false, (void *)fuse) < 0) { lxcFuseDestroy(fuse); return -1; } return 0; } void lxcFreeFuse(struct virLXCFuse **f) { struct virLXCFuse *fuse = *f; /* lxcFuseRun thread create success */ if (fuse) { /* exit fuse_loop, lxcFuseRun thread may try to destroy * fuse->fuse at the same time,so add a lock here. */ VIR_WITH_MUTEX_LOCK_GUARD(&fuse->lock) { if (fuse->fuse) fuse_exit(fuse->fuse); } g_free(fuse->mountpoint); g_free(*f); } } #else int lxcSetupFuse(struct virLXCFuse **f G_GNUC_UNUSED, virDomainDef *def G_GNUC_UNUSED) { return 0; } int lxcStartFuse(struct virLXCFuse *f G_GNUC_UNUSED) { return 0; } void lxcFreeFuse(struct virLXCFuse **f G_GNUC_UNUSED) { } #endif