Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/file-server/btrfs/subvolume.ts
1539 views
1
/*
2
A subvolume
3
*/
4
5
import { type Filesystem, DEFAULT_SUBVOLUME_SIZE } from "./filesystem";
6
import refCache from "@cocalc/util/refcache";
7
import { sudo } from "./util";
8
import { join, normalize } from "path";
9
import { SubvolumeFilesystem } from "./subvolume-fs";
10
import { SubvolumeBup } from "./subvolume-bup";
11
import { SubvolumeSnapshots } from "./subvolume-snapshots";
12
import { SubvolumeQuota } from "./subvolume-quota";
13
import { exists } from "@cocalc/backend/misc/async-utils-node";
14
15
import getLogger from "@cocalc/backend/logger";
16
17
const logger = getLogger("file-server:btrfs:subvolume");
18
19
interface Options {
20
filesystem: Filesystem;
21
name: string;
22
}
23
24
export class Subvolume {
25
public readonly name: string;
26
27
public readonly filesystem: Filesystem;
28
public readonly path: string;
29
public readonly fs: SubvolumeFilesystem;
30
public readonly bup: SubvolumeBup;
31
public readonly snapshots: SubvolumeSnapshots;
32
public readonly quota: SubvolumeQuota;
33
34
constructor({ filesystem, name }: Options) {
35
this.filesystem = filesystem;
36
this.name = name;
37
this.path = join(filesystem.opts.mount, name);
38
this.fs = new SubvolumeFilesystem(this);
39
this.bup = new SubvolumeBup(this);
40
this.snapshots = new SubvolumeSnapshots(this);
41
this.quota = new SubvolumeQuota(this);
42
}
43
44
init = async () => {
45
if (!(await exists(this.path))) {
46
logger.debug(`creating ${this.name} at ${this.path}`);
47
await sudo({
48
command: "btrfs",
49
args: ["subvolume", "create", this.path],
50
});
51
await this.chown(this.path);
52
await this.quota.set(
53
this.filesystem.opts.defaultSize ?? DEFAULT_SUBVOLUME_SIZE,
54
);
55
}
56
};
57
58
close = () => {
59
// @ts-ignore
60
delete this.filesystem;
61
// @ts-ignore
62
delete this.name;
63
// @ts-ignore
64
delete this.path;
65
// @ts-ignore
66
delete this.snapshotsDir;
67
for (const sub of ["fs", "bup", "snapshots", "quota"]) {
68
this[sub].close?.();
69
delete this[sub];
70
}
71
};
72
73
private chown = async (path: string) => {
74
await sudo({
75
command: "chown",
76
args: [`${process.getuid?.() ?? 0}:${process.getgid?.() ?? 0}`, path],
77
});
78
};
79
80
// this should provide a path that is guaranteed to be
81
// inside this.path on the filesystem or throw error
82
// [ ] TODO: not sure if the code here is sufficient!!
83
normalize = (path: string) => {
84
return join(this.path, normalize(path));
85
};
86
}
87
88
const cache = refCache<Options & { noCache?: boolean }, Subvolume>({
89
name: "btrfs-subvolumes",
90
createKey: ({ name }) => name,
91
createObject: async (options: Options) => {
92
const subvolume = new Subvolume(options);
93
await subvolume.init();
94
return subvolume;
95
},
96
});
97
98
export async function subvolume(
99
options: Options & { noCache?: boolean },
100
): Promise<Subvolume> {
101
return await cache(options);
102
}
103
104