huge file system rewrite, i lost a lot of track; permissions and user system

This commit is contained in:
2025-05-07 23:36:34 -04:00
parent 22b9e1f3d5
commit 8c2f1de028
8 changed files with 347 additions and 289 deletions

21
src/rt/crypto/generate.ts Normal file
View File

@ -0,0 +1,21 @@
type SHA256_String = string
class Crypto {
protected inner: string
constructor(inner: string) {
this.inner = inner
}
public async sha256_string(): Promise<string> {
const encoder = new TextEncoder()
const hash = await crypto.subtle.digest("SHA-256", encoder.encode(this.inner))
const hash_as_uint8 = new Uint8Array(hash)
return Array.from(hash_as_uint8).map(byte => byte.toString(16).padStart(2, "0")).join("")
}
}
export default Crypto
export {
type SHA256_String
}

View File

@ -1,11 +1,16 @@
import { Permissions } from "./rfwfs/main" import rfwfs, { DirectoryInRoot, Permissions } from "./rfwfs/main"
import rfwfs from "./rfwfs/main"
const time_now = (Date.now()/1000) | 0 const time_now = (Date.now()/1000) | 0
const fs = new rfwfs() const fs = new rfwfs()
fs.add_file(default_name, default_permissions) const bin = rfwfs.directory_in_root({
name: "bin",
timestamp: time_now,
permissions: Permissions.r | Permissions.w
})
fs.push_bulk_unsafe([
bin.dir as DirectoryInRoot
])
export default fs export default fs

View File

@ -1,21 +0,0 @@
const enum PushStatus {
Ok,
Duplicate,
Denied,
}
const enum ReadStatus {
Ok,
NotFound,
Denied,
}
const enum ExecuteStatus {
Ok,
Panic,
Denied,
}
export {
ExecuteStatus,
ReadStatus,
PushStatus,
}

View File

@ -1,11 +0,0 @@
async function hash(inner_as_string: string) {
const encoder = new TextEncoder()
const hash = await crypto.subtle.digest("SHA-256", encoder.encode(inner_as_string))
const hash_as_uint8 = new Uint8Array(hash)
return Array.from(hash_as_uint8).map(byte => byte.toString(16).padStart(2, "0")).join("")
}
export default async function generate_sha256(inner_as_string: string) {
const sha256 = await hash(inner_as_string)
return sha256
}

View File

@ -1,7 +1,11 @@
import { type Entry } from "./main" import { type Entry } from "./main"
import { wrap_bsearch, type WrapBSearch } from "./wrap" import wrap, { WrapResult } from "./wrap"
export default function directory_search<T extends Entry>(entry_collection: T[], file_name: string): WrapBSearch<T> | undefined { function wrap_bsearch<T extends Entry>(index: number, result: T): WrapResult<T, number> {
return wrap(result, index)
}
export default function directory_search<T extends Entry>(entry_collection: T[], file_name: string): WrapResult<T, number> | undefined {
let start = 0 let start = 0
let end = entry_collection.length-1 let end = entry_collection.length-1
while (start<=end) { while (start<=end) {

View File

@ -1,5 +1,4 @@
import { ReadStatus, PushStatus, ExecuteStatus } from "./enum/status" import wrap, { type WrapResult, ConstEnum, Option } from "./wrap"
import { wrap_entry, wrap_none, wrap_binary, type WrapResultEntry, type WrapResultNone, type WrapBinary } from "./wrap"
import directory_search from "./index" import directory_search from "./index"
@ -15,230 +14,192 @@ const enum Permissions {
x = 1<<2, x = 1<<2,
none = 1<<3, none = 1<<3,
} }
const enum ROOT_ID { TRUNK = "/" } const enum ROOT_ID {
TRUNK = "/",
NAME = "root"
}
type FileInner = string | number const enum PushStatus {
type BinaryError = string Ok,
Duplicate,
Denied,
}
const enum ReadStatus {
Ok,
NotFound,
Denied,
}
type BinaryEntry = () => EntryStripped interface UserPermissions {
type BinaryLambda = (binary_entry: BinaryEntry) => void readonly root: Permissions.r | Permissions.w | Permissions.x, //Flip OR
[index: string]: Permissions
}
type Directory<T extends Entry> = EntryCollection<T> interface Entry<T extends EntryType = EntryType, N = EntryValue<string>> {
type DirectoryAny = EntryCollection<Entry> readonly type: T,
type DirectoryAnyDepth = EntryCollection<DirectoryAny> permissions: UserPermissions,
timestamp: number,
name: N
}
interface EntryStripped { interface DirectoryContainer<T> extends Entry {
readonly type: EntryType, files: EntryValue<Entry[]>,
parent: T | null
}
type Directory<T extends Entry> = DirectoryContainer<RfwfsDirectory<T>>
type DirectoryInRoot = DirectoryContainer<Root>
interface Root extends Entry<EntryType.Root, ROOT_ID.TRUNK> {
timestamp: number,
parent: null,
files: EntryValue<Entry[]>,
}
interface DirectoryInRootProperties {
permissions: Permissions, permissions: Permissions,
timestamp: EntryValue<number>, name: string
name: EntryValue<string>, timestamp: number,
} }
interface Entry<T = DirectoryAny | null> extends EntryStripped { interface DirectoryProperties<T extends Entry> extends DirectoryInRootProperties {
parent: T, parent: RfwfsDirectory<T>,
} }
interface EntryFile extends Entry { interface FileProperties extends Entry {
inner: EntryValue<FileInner>,
hash: string,
} }
interface EntryCollection<T extends Entry> extends Entry { /** Other directory types that can be treated as a single arbitrary directory.
inner: RfwfsDirectory<T>,
Do not cast.
*/
type DirectoryAssociates<T extends Entry> = Directory<T> | DirectoryInRoot | Root
/** Other entry types that can be treated as a single arbitrary entry.
Do not cast.
*/
type EntryAssociates = Entry | Root
type WrapResultEntry<T extends Entry, U> = WrapResult<T | undefined, U>
type WrapResultNone<T> = WrapResult<Option.None, T>
function wrap_entry<T extends ConstEnum, U extends Entry>(status: T, result?: U): WrapResultEntry<U, T> {
return wrap(result, status)
} }
interface EntryBinary extends Entry { function wrap_none<T extends ConstEnum>(status: T): WrapResultNone<T> {
inner: RfwfsBinary return wrap(Option.None, status)
} }
interface Root<T extends Entry> { function fs_dir_sort<T extends Entry>(dir: DirectoryAssociates<T>) {
readonly type: EntryType, dir.files.inner.sort((a,z) => a.name.inner.localeCompare(z.name.inner))
readonly permissions: Permissions,
readonly timestamp: EntryValueRoot<number>,
readonly parent: null,
readonly inner: RfwfsRootDirectory<T>,
readonly name: ROOT_ID.TRUNK,
} }
function strip_entry<T extends Entry>(entry: T): EntryStripped { function fs_dir_clone<T extends Entry>(dir: DirectoryAssociates<T>, file_name: string): WrapResultEntry<T, ReadStatus> {
return { const clone_find = directory_search(dir.files.inner, file_name)
type: entry.type,
permissions: entry.permissions,
timestamp: entry.timestamp,
name: entry.name,
}
}
function fs_dir_clone<T extends Entry>(directory: T[], file_name: string): WrapResultEntry<T, ReadStatus> {
const clone_find = directory_search(directory, file_name)
if (clone_find) { if (clone_find) {
return wrap_entry(ReadStatus.Ok, { ...clone_find.result }) return wrap_entry(ReadStatus.Ok, { ...clone_find.result as T })
} }
return wrap_entry(ReadStatus.NotFound) return wrap_entry(ReadStatus.NotFound)
} }
function fs_dir_find<T extends Entry>(directory: T[], file_name: string): WrapResultEntry<T, ReadStatus> { function fs_dir_find<T extends Entry>(dir: DirectoryAssociates<T>, file_name: string): WrapResultEntry<T, ReadStatus> {
const file_search = directory_search(directory, file_name) const file_search = directory_search(dir.files.inner, file_name)
if (file_search) { if (file_search) {
return wrap_entry(ReadStatus.Ok, file_search.result) return wrap_entry(ReadStatus.Ok, file_search.result as T)
} }
return wrap_entry(ReadStatus.NotFound) return wrap_entry(ReadStatus.NotFound)
} }
function fs_dir_push<T extends Entry, E extends T>(directory: T[], entry: E) { function fs_dir_push<T extends Entry>(dir: DirectoryAssociates<T>, entry: Entry) {
const no_duplicates = directory_search(directory, entry.name.inner) const no_duplicates = directory_search(dir.files.inner, entry.name.inner)
if (!no_duplicates) { if (!no_duplicates) {
directory.push(entry) dir.files.inner.push(entry)
directory.sort() fs_dir_sort(dir)
return wrap_none(PushStatus.Ok) return wrap_none(PushStatus.Ok)
} }
return wrap_none(PushStatus.Duplicate) return wrap_none(PushStatus.Duplicate)
} }
function fs_dir_pop<T extends Entry>(directory: T[], file_name: string): WrapResultEntry<T, ReadStatus> { function fs_dir_pop<T extends Entry>(dir: DirectoryAssociates<T>, file_name: string): WrapResultEntry<T, ReadStatus> {
const pop_find = directory_search(directory, file_name) const pop_find = directory_search(dir.files.inner, file_name)
if (pop_find) { if (pop_find) {
directory.splice(pop_find.status, 1) dir.files.inner.splice(pop_find.status, 1)
return wrap_entry(ReadStatus.Ok, pop_find.result) return wrap_entry(ReadStatus.Ok, pop_find.result as T)
} }
return wrap_entry(ReadStatus.NotFound) return wrap_entry(ReadStatus.NotFound)
} }
class EntryValue<T> { class EntryValue<V> {
public inner: T; public inner: V;
protected entry: Entry; protected user_perms: UserPermissions;
constructor(entry: Entry, inner_default: T) { constructor(user: UserPermissions, value: V) {
this.inner = inner_default this.inner = value
this.entry = entry this.user_perms = user
} }
/** public read(): V | undefined {
Safe write return rfwfs_lib.read_access(this.user_perms.permissions) ? this.inner : undefined
*/ }
public write<I extends T>(item: I): boolean {
if (rfwfs.write_access(this.entry.permissions)) { public write<T extends V>(new_value: T): boolean {
this.inner = item if (rfwfs_lib.write_access(this.user_perms.permissions)) {
this.inner = new_value
return true return true
} }
return false return false
} }
/**
Convert the inner file value
Same as `write` but mutates the inner value `T` into a `FileInner`
*/
public write_into(item: FileInner): boolean {
return this.write(item as T)
}
public read(): T | undefined {
return rfwfs.write_access(this.entry.permissions) ? this.inner : undefined
}
}
class EntryValueRoot<T> {
protected inner: T;
constructor(inner: T) {
this.inner = inner
}
public read(): T {
return this.inner
}
} }
class RfwfsDirectory<T extends Entry> { class RfwfsDirectory<T extends Entry> {
public directory: T[]; public dir: DirectoryAssociates<T>;
protected entry: Entry;
constructor(entry: Entry, directory: T[]) { constructor(dir: DirectoryAssociates<T>) {
this.directory = directory this.dir = dir
this.entry = entry
} }
public sort() { public sort() {
this.directory.sort((a,z) => a.name.inner.localeCompare(z.name.inner)) fs_dir_sort(this.dir)
} }
public clone(file_name: string): WrapResultEntry<T, ReadStatus> { public clone(file_name: string): WrapResultEntry<Entry, ReadStatus> {
if (rfwfs.read_write_access(this.entry.permissions)) { if (rfwfs.read_write_access(this.dir.permissions)) {
return fs_dir_clone(this.directory, file_name) return fs_dir_clone(this.dir, file_name)
} }
return wrap_entry(ReadStatus.Denied) return wrap_entry(ReadStatus.Denied)
} }
public find(file_name: string): WrapResultEntry<T, ReadStatus> { public find(file_name: string): WrapResultEntry<Entry, ReadStatus> {
if (rfwfs.read_write_access(this.entry.permissions)) { if (rfwfs.read_write_access(this.dir.permissions)) {
return fs_dir_find(this.directory, file_name) return fs_dir_find(this.dir, file_name)
} }
return wrap_entry(ReadStatus.Denied) return wrap_entry(ReadStatus.Denied)
} }
public push<E extends T>(entry: E): WrapResultNone<PushStatus> { public push<E extends Entry>(entry: E): WrapResultNone<PushStatus> {
if (rfwfs.read_write_access(this.entry.permissions)) { if (rfwfs.read_write_access(this.dir.permissions)) {
return fs_dir_push(this.directory, entry) return fs_dir_push(this.dir, entry)
} }
return wrap_none(PushStatus.Denied) return wrap_none(PushStatus.Denied)
} }
public pop(file_name: string): WrapResultEntry<T, ReadStatus> { public pop(file_name: string): WrapResultEntry<Entry, ReadStatus> {
if (rfwfs.read_write_access(this.entry.permissions)) { if (rfwfs.read_write_access(this.dir.permissions)) {
fs_dir_pop(this.directory, file_name) fs_dir_pop(this.dir, file_name)
} }
return wrap_entry(ReadStatus.Denied) return wrap_entry(ReadStatus.Denied)
} }
}
class RfwfsRootDirectory<T extends Entry> { public push_bulk_unsafe(dirs: T[]) {
public directory: T[]; dirs.forEach(dir => this.dir.files.inner.push(dir))
this.sort()
constructor() {
this.directory = []
} }
public sort() { public push_unsafe(dir: T) {
this.directory.sort((a,z) => a.name.inner.localeCompare(z.name.inner)) this.dir.files.inner.push(dir)
} this.sort()
public clone(file_name: string): WrapResultEntry<T, ReadStatus> {
return fs_dir_clone(this.directory, file_name)
}
public find(file_name: string): WrapResultEntry<T, ReadStatus> {
return fs_dir_find(this.directory, file_name)
}
public push<E extends T>(entry: E): WrapResultNone<PushStatus> {
return fs_dir_push(this.directory, entry)
}
public pop(file_name: string): WrapResultEntry<T, ReadStatus> {
return fs_dir_pop(this.directory, file_name)
}
}
class RfwfsBinary {
public lambda: BinaryLambda;
protected entry: Entry;
constructor(entry: Entry, lambda: BinaryLambda) {
this.lambda = lambda
this.entry = entry
}
public execute(): WrapBinary {
if (rfwfs.execute_access(this.entry.permissions)) {
try {
this.lambda(() => strip_entry(this.entry))
} catch(binary_e) {
return wrap_binary(ExecuteStatus.Panic, (binary_e as object).toString())
}
return wrap_binary(ExecuteStatus.Ok)
}
return wrap_binary(ExecuteStatus.Denied)
} }
} }
@ -268,88 +229,105 @@ class rfwfs_lib {
public static read_write_access(permissions: Permissions): boolean { public static read_write_access(permissions: Permissions): boolean {
return rfwfs.read_access(permissions) && rfwfs.write_access(permissions) return rfwfs.read_access(permissions) && rfwfs.write_access(permissions)
} }
public static directory_in_root(properties: DirectoryInRootProperties): RfwfsDirectory<DirectoryInRoot> {
class dir<P, F extends Entry> {
public parent: P;
public permissions: Permissions;
public timestamp: number;
public files: EntryValue<F[]>;
public name: EntryValue<string>;
constructor(permissions: Permissions, timestamp: number, name: string, parent: P, files: F[]) {
this.parent = parent
this.permissions = permissions
this.timestamp = timestamp
this.files = new EntryValue(this.permissions, files)
}
}
// const dir_o = { type: EntryType.Directory } as DirectoryInRoot
// dir_o.parent = null
// dir_o.permissions = properties.permissions
// dir_o.timestamp = properties.timestamp
// dir_o.files = new EntryValue(dir_o, [])
// dir_o.name = new EntryValue(dir_o, properties.name)
// return new RfwfsDirectory(dir_o)
}
public static directory<T extends Entry>(properties: DirectoryProperties<T>): RfwfsDirectory<T> {
const dir_o = { type: EntryType.Directory } as Directory<T>
dir_o.parent = properties.parent
dir_o.permissions = properties.permissions
dir_o.timestamp = properties.timestamp
dir_o.files = new EntryValue(dir_o, [])
dir_o.name = new EntryValue(dir_o, properties.name)
return new RfwfsDirectory(dir_o)
}
public static file(properties: FileProperties) {
}
} }
class rfwfs<T extends Entry> extends rfwfs_lib { class rfwfs extends rfwfs_lib {
public root: Root<T>; public root: Root;
constructor() { constructor() {
super() super()
this.root = { this.root = { type: EntryType.Root } as Root
type: EntryType.Root, this.root.permissions = Permissions.r | Permissions.w
permissions: Permissions.r | Permissions.w, this.root.timestamp = (Date.now()/1000) | 0
timestamp: new EntryValueRoot((Date.now()/1000) | 0), this.root.parent = null
parent: null, this.root.files = new EntryValue(this.root, [])
inner: new RfwfsRootDirectory(), this.root.name = ROOT_ID.TRUNK
name: ROOT_ID.TRUNK,
} as Root<T>
} }
public push_directory_into_root<T extends Entry>(dir: EntryCollection<T>): EntryCollection<T> { public sort() {
this.root.inner.push(dir) fs_dir_sort(this.root)
return dir
} }
public static file( public clone(file_name: string): WrapResultEntry<Entry, ReadStatus> {
default_name: string, if (rfwfs.read_write_access(this.root.permissions)) {
default_permissions: Permissions, return fs_dir_clone(this.root, file_name)
default_parent: DirectoryAny, }
default_timestamp?: number, return wrap_entry(ReadStatus.Denied)
default_inner?: FileInner
): EntryFile {
const file = { type: EntryType.File } as EntryFile
file.hash = "0"
file.permissions = default_permissions
file.parent = default_parent
file.timestamp = new EntryValue(file, default_timestamp ? default_timestamp : (Date.now()/1000) | 0)
file.inner = new EntryValue(file, default_inner ? default_inner : "")
file.name = new EntryValue(file, default_name)
return file
} }
public static directory<T extends Entry>( public find(file_name: string): WrapResultEntry<Entry, ReadStatus> {
default_name: string, if (rfwfs.read_write_access(this.root.permissions)) {
default_permissions: Permissions, return fs_dir_find(this.root, file_name)
default_parent: DirectoryAny, }
default_timestamp?: number, return wrap_entry(ReadStatus.Denied)
default_inner?: T[]
): EntryCollection<T> {
const directory = { type: EntryType.Directory } as EntryCollection<T>
directory.parent = default_parent
directory.permissions = default_permissions
directory.timestamp = new EntryValue(directory, default_timestamp ? default_timestamp : (Date.now()/1000) | 0)
directory.inner = new RfwfsDirectory(directory, default_inner ? default_inner : [])
directory.name = new EntryValue(directory, default_name)
return directory
} }
public static binary( public push<T extends Entry>(entry: T): WrapResultNone<PushStatus> {
default_name: string, if (rfwfs.read_write_access(this.root.permissions)) {
default_permissions: Permissions, return fs_dir_push(this.root, entry)
default_parent: DirectoryAny, }
default_timestamp?: number, return wrap_none(PushStatus.Denied)
default_inner?: BinaryLambda }
): EntryBinary {
const binary = { type: EntryType.Binary } as EntryBinary public pop(file_name: string): WrapResultEntry<Entry, ReadStatus> {
binary.parent = default_parent if (rfwfs.read_write_access(this.root.permissions)) {
binary.permissions = default_permissions fs_dir_pop(this.root, file_name)
binary.timestamp = new EntryValue(binary, default_timestamp ? default_timestamp : (Date.now()/1000) | 0) }
binary.inner = new RfwfsBinary(binary, default_inner ? default_inner : () => {}) return wrap_entry(ReadStatus.Denied)
binary.name = new EntryValue(binary, default_name) }
return binary
public push_bulk_unsafe(dirs: DirectoryInRoot[]) {
dirs.forEach(dir => this.root.files.inner.push(dir))
this.sort()
}
public push_unsafe(dir: DirectoryInRoot) {
this.root.files.inner.push(dir)
this.sort()
} }
} }
export default rfwfs export default rfwfs
export { export {
type DirectoryAnyDepth, type DirectoryInRoot,
type EntryCollection,
type RfwfsDirectory, type RfwfsDirectory,
type DirectoryAny,
type BinaryError,
type Directory, type Directory,
type FileInner,
type EntryFile,
type Entry, type Entry,
Permissions, Permissions,
EntryType, EntryType,

112
src/rt/rfwfs/users.ts Normal file
View File

@ -0,0 +1,112 @@
import { ROOT_ID } from "./main";
import Crypto, { SHA256_String } from "../crypto/generate";
import wrap, { type WrapResult } from "./wrap";
const enum SysGroups {
Wheel,
Users,
}
const enum GroupSearch {
NotFound,
WheelResult,
UsersResult,
}
type WrapUserSearch = WrapResult<User | undefined, GroupSearch>
const wheel: User[] = []
const users: User[] = []
function wrap_user_search(status: GroupSearch, result?: User): WrapUserSearch {
return wrap(result, status)
}
function groups_find_user(user_name: string): WrapUserSearch {
const exist_in_wheel = wheel.find(user => user.get_uname() === user_name)
if (exist_in_wheel) {
return wrap_user_search(GroupSearch.WheelResult, exist_in_wheel)
}
const exist_in_users = users.find(user => user.get_uname() === user_name)
if (exist_in_users) {
return wrap_user_search(GroupSearch.UsersResult, exist_in_users)
}
return wrap_user_search(GroupSearch.NotFound)
}
function group_add(new_user: User, group: User[]): GroupSearch {
const dups = groups_find_user(new_user.get_uname())
if (dups.status === GroupSearch.NotFound) {
group.push(new_user)
}
return dups.status
}
function group_remove(user_name: string, group: User[]): boolean {
for (let i = 0; i<group.length; i++) {
if (group[i].get_uname() === user_name) {
group.splice(i, 1)
return true
}
}
return false
}
function group_wheel_add(new_user: User): GroupSearch {
return group_add(new_user, wheel)
}
function group_wheel_remove(user_name: string): boolean {
return group_remove(user_name, wheel)
}
function group_users_add(new_user: User): GroupSearch {
return group_add(new_user, users)
}
function group_users_remove(user_name: string): boolean {
return group_remove(user_name, users)
}
class user_lib {
public static current: string = ROOT_ID.NAME
}
class User extends user_lib {
private name: string;
private password?: SHA256_String;
constructor(name: string, password?: SHA256_String) {
super()
this.name = name
this.password = password
}
public get_uname() {
return this.name
}
public set_uname(new_username: string) {
const search = groups_find_user(new_username)
if (search.status === GroupSearch.NotFound) {
}
}
public async set_password(new_password?: string): Promise<void> {
if (new_password) {
this.password = await new Crypto(new_password).sha256_string()
} else {
this.password = undefined
}
}
}
export default User
export {
group_wheel_remove,
group_users_remove,
group_wheel_add,
group_users_add,
SysGroups,
}

View File

@ -1,18 +1,10 @@
import { type BinaryError, type Entry } from "./main"
import { ExecuteStatus } from "./enum/status"
const enum Option { const enum Option {
Ok,
None, None,
Some,
} }
type ConstEnum = number type ConstEnum = number
type WrapResultEntry<T extends Entry, U> = WrapResult<T | undefined, U>
type WrapBSearch<T extends Entry> = WrapResult<T, number>
type WrapResultNone<T> = WrapResult<Option.None, T>
type WrapBinary = WrapResult<ExecuteStatus, BinaryError | undefined>
interface WrapResult<T, U> { interface WrapResult<T, U> {
/** The resulting value if `U` is a success status */ /** The resulting value if `U` is a success status */
readonly result: T, readonly result: T,
@ -24,31 +16,9 @@ function wrap<T, U>(result: T, some: U): WrapResult<T, U> {
return { result: result, status: some } return { result: result, status: some }
} }
function wrap_bsearch<T extends Entry>(index: number, result: T): WrapBSearch<T> {
return wrap(result, index)
}
function wrap_entry<T extends ConstEnum, U extends Entry>(status: T, result?: U): WrapResultEntry<U, T> {
return wrap(result, status)
}
function wrap_none<T extends ConstEnum>(status: T): WrapResultNone<T> {
return wrap(Option.None, status)
}
function wrap_binary(status: ExecuteStatus, result?: BinaryError): WrapBinary {
return wrap(status, result)
}
export default wrap export default wrap
export { export {
type WrapResultEntry,
type WrapResultNone,
type WrapBSearch,
type WrapResult, type WrapResult,
type WrapBinary, type ConstEnum,
wrap_bsearch, Option,
wrap_binary, }
wrap_entry,
wrap_none,
}