Path: blob/master/src/packages/file-server/btrfs/subvolume.ts
1539 views
/*1A subvolume2*/34import { type Filesystem, DEFAULT_SUBVOLUME_SIZE } from "./filesystem";5import refCache from "@cocalc/util/refcache";6import { sudo } from "./util";7import { join, normalize } from "path";8import { SubvolumeFilesystem } from "./subvolume-fs";9import { SubvolumeBup } from "./subvolume-bup";10import { SubvolumeSnapshots } from "./subvolume-snapshots";11import { SubvolumeQuota } from "./subvolume-quota";12import { exists } from "@cocalc/backend/misc/async-utils-node";1314import getLogger from "@cocalc/backend/logger";1516const logger = getLogger("file-server:btrfs:subvolume");1718interface Options {19filesystem: Filesystem;20name: string;21}2223export class Subvolume {24public readonly name: string;2526public readonly filesystem: Filesystem;27public readonly path: string;28public readonly fs: SubvolumeFilesystem;29public readonly bup: SubvolumeBup;30public readonly snapshots: SubvolumeSnapshots;31public readonly quota: SubvolumeQuota;3233constructor({ filesystem, name }: Options) {34this.filesystem = filesystem;35this.name = name;36this.path = join(filesystem.opts.mount, name);37this.fs = new SubvolumeFilesystem(this);38this.bup = new SubvolumeBup(this);39this.snapshots = new SubvolumeSnapshots(this);40this.quota = new SubvolumeQuota(this);41}4243init = async () => {44if (!(await exists(this.path))) {45logger.debug(`creating ${this.name} at ${this.path}`);46await sudo({47command: "btrfs",48args: ["subvolume", "create", this.path],49});50await this.chown(this.path);51await this.quota.set(52this.filesystem.opts.defaultSize ?? DEFAULT_SUBVOLUME_SIZE,53);54}55};5657close = () => {58// @ts-ignore59delete this.filesystem;60// @ts-ignore61delete this.name;62// @ts-ignore63delete this.path;64// @ts-ignore65delete this.snapshotsDir;66for (const sub of ["fs", "bup", "snapshots", "quota"]) {67this[sub].close?.();68delete this[sub];69}70};7172private chown = async (path: string) => {73await sudo({74command: "chown",75args: [`${process.getuid?.() ?? 0}:${process.getgid?.() ?? 0}`, path],76});77};7879// this should provide a path that is guaranteed to be80// inside this.path on the filesystem or throw error81// [ ] TODO: not sure if the code here is sufficient!!82normalize = (path: string) => {83return join(this.path, normalize(path));84};85}8687const cache = refCache<Options & { noCache?: boolean }, Subvolume>({88name: "btrfs-subvolumes",89createKey: ({ name }) => name,90createObject: async (options: Options) => {91const subvolume = new Subvolume(options);92await subvolume.init();93return subvolume;94},95});9697export async function subvolume(98options: Options & { noCache?: boolean },99): Promise<Subvolume> {100return await cache(options);101}102103104