Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/file-server/zfs/properties.ts
1447 views
1
import { exec } from "./util";
2
import { filesystemDataset } from "./names";
3
import { get, set } from "./db";
4
import { MIN_QUOTA } from "./config";
5
import { primaryKey, type PrimaryKey } from "./types";
6
7
export async function setQuota({
8
// quota in **number of bytes**.
9
// If quota is smaller than actual dataset, then the quota is set to what is
10
// actually used (plus 10 MB), hopefully allowing user to delete some data.
11
// The quota is never less than MIN_QUOTA.
12
// The value stored in database is *also* then set to this amount.
13
// So this is not some magic fire and forget setting, but something
14
// that cocalc should regularly call when starting the filesystem.
15
quota,
16
noSync,
17
...fs
18
}: {
19
quota: number;
20
noSync?: boolean;
21
} & PrimaryKey) {
22
const pk = primaryKey(fs);
23
// this will update current usage in the database
24
await syncProperties(pk);
25
const { pool, used_by_dataset } = get(pk);
26
const used = (used_by_dataset ?? 0) + 10 * 1024;
27
if (quota < used) {
28
quota = used!;
29
}
30
quota = Math.max(MIN_QUOTA, quota);
31
try {
32
await exec({
33
verbose: true,
34
command: "sudo",
35
args: [
36
"zfs",
37
"set",
38
// refquota so snapshots don't count against the user
39
`refquota=${quota}`,
40
filesystemDataset({ pool, ...pk }),
41
],
42
});
43
} finally {
44
// this sets quota in database in bytes to whatever was just set above.
45
await syncProperties(pk);
46
}
47
}
48
49
// Sync with ZFS the properties for the given filesystem by
50
// setting the database to what is in ZFS:
51
// - total space used by snapshots
52
// - total space used by dataset
53
// - the quota
54
export async function syncProperties(fs: PrimaryKey) {
55
const pk = primaryKey(fs);
56
const { pool, archived } = get(pk);
57
if (archived) {
58
// they can't have changed
59
return;
60
}
61
set({
62
...pk,
63
...(await zfsGetProperties(filesystemDataset({ pool, ...pk }))),
64
});
65
}
66
67
export async function zfsGetProperties(dataset: string): Promise<{
68
used_by_snapshots: number;
69
used_by_dataset: number;
70
quota: number | null;
71
}> {
72
const { stdout } = await exec({
73
command: "zfs",
74
args: [
75
"list",
76
dataset,
77
"-j",
78
"--json-int",
79
"-o",
80
"usedsnap,usedds,refquota",
81
],
82
});
83
const x = JSON.parse(stdout);
84
const { properties } = x.datasets[dataset];
85
return {
86
used_by_snapshots: properties.usedbysnapshots.value,
87
used_by_dataset: properties.usedbydataset.value,
88
quota: properties.refquota.value ? properties.refquota.value : null,
89
};
90
}
91
92
export async function mountFilesystem(fs: PrimaryKey) {
93
const pk = primaryKey(fs);
94
const { pool } = get(pk);
95
try {
96
await exec({
97
command: "sudo",
98
args: ["zfs", "mount", filesystemDataset({ pool, ...pk })],
99
what: { ...pk, desc: "mount filesystem" },
100
});
101
} catch (err) {
102
if (`${err}`.includes("already mounted")) {
103
// fine
104
return;
105
}
106
throw err;
107
}
108
}
109
110
export async function unmountFilesystem(fs: PrimaryKey) {
111
const pk = primaryKey(fs);
112
const { pool } = get(pk);
113
try {
114
await exec({
115
verbose: true,
116
command: "sudo",
117
args: ["zfs", "unmount", filesystemDataset({ pool, ...pk })],
118
what: { ...pk, desc: "unmount filesystem" },
119
});
120
} catch (err) {
121
if (`${err}`.includes("not currently mounted")) {
122
// fine
123
} else {
124
throw err;
125
}
126
}
127
}
128
129