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