riju/backend/users.js

92 lines
2.2 KiB
JavaScript

import { spawn } from "child_process";
import fs from "fs";
import os from "os";
import AsyncLock from "async-lock";
import _ from "lodash";
import parsePasswd from "parse-passwd";
import { privilegedUseradd, run } from "./util.js";
// Keep in sync with system/src/riju-system-privileged.c
export const MIN_UID = 2000;
export const MAX_UID = 65000;
const CUR_UID = os.userInfo().uid;
let availIds = null;
let nextId = null;
let lock = new AsyncLock();
async function readExistingUsers(log) {
availIds = parsePasswd(
await new Promise((resolve, reject) =>
fs.readFile("/etc/passwd", "utf-8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
)
)
.filter(({ username }) => username.startsWith("riju"))
.map(({ uid }) => parseInt(uid))
.filter((uid) => !isNaN(uid) && uid >= MIN_UID && uid < MAX_UID)
.reverse();
nextId = (_.max(availIds) || MIN_UID - 1) + 1;
log(`Found ${availIds.length} existing users, next is riju${nextId}`);
}
async function createUser(log) {
if (nextId >= MAX_UID) {
throw new Error("too many users");
}
const uid = nextId;
await run(privilegedUseradd(uid), log);
log(`Created new user with ID ${uid}`);
nextId += 1;
return uid;
}
export async function ignoreUsers(uids, log) {
await lock.acquire("key", async () => {
if (availIds === null || nextId === null) {
await readExistingUsers(log);
}
const uidSet = new Set(uids);
if (uidSet.size > 0) {
const plural = uidSet.size !== 1 ? "s" : "";
log(
`Ignoring user${plural} from open session${plural}: ${Array.from(uidSet)
.sort()
.map((uid) => `riju${uid}`)
.join(", ")}`
);
}
availIds = availIds.filter((uid) => !uidSet.has(uid));
});
}
export async function borrowUser(log) {
return await lock.acquire("key", async () => {
if (availIds === null || nextId === null) {
await readExistingUsers(log);
}
let uid;
if (availIds.length > 0) {
uid = availIds.pop();
} else {
uid = await createUser(log);
}
return {
uid,
returnUID: async () => {
await lock.acquire("key", () => {
availIds.push(uid);
});
},
};
});
}