Skip to content

Commit aace1ed

Browse files
committed
Inodes are now a fixed 4096 bytes
The inode format is now version 5 Fixed `Attribute`/`Attributes` Updated memium
1 parent 53117fa commit aace1ed

File tree

5 files changed

+142
-57
lines changed

5 files changed

+142
-57
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"buffer": "^6.0.3",
7272
"eventemitter3": "^5.0.1",
7373
"kerium": "^1.3.4",
74-
"memium": "^0.1.10",
74+
"memium": "^0.2.0",
7575
"readable-stream": "^4.5.2",
7676
"utilium": "^2.2.3"
7777
},

src/internal/inode.ts

Lines changed: 126 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { withErrno } from 'kerium';
22
import { crit, warn } from 'kerium/log';
33
import { field, packed, sizeof, struct, types as t, type Struct } from 'memium';
44
import { decodeUTF8, encodeUTF8, pick } from 'utilium';
5-
import { BufferView } from 'utilium/buffer.js';
5+
import { BufferView, initView } from 'utilium/buffer.js';
66
import * as c from '../vfs/constants.js';
77
import { Stats, type StatsLike } from '../vfs/stats.js';
88
import { defaultContext, type V_Context } from './contexts.js';
@@ -13,33 +13,42 @@ import { defaultContext, type V_Context } from './contexts.js';
1313
*/
1414
export const rootIno = 0;
1515

16-
const maxAttrValueSize = 1024;
16+
/** 4 KiB minus static inode data */
17+
const maxDynamicData = 3968;
1718

1819
@struct(packed)
19-
class Attribute extends BufferView {
20-
@t.uint32 protected accessor keySize!: number;
21-
@t.uint32 protected accessor valueSize!: number;
22-
23-
@t.char(0, { countedBy: 'keySize' }) protected accessor _name!: Uint8Array;
20+
class Attribute<B extends ArrayBufferLike = ArrayBufferLike> extends Uint8Array<B> {
21+
@t.uint32 public accessor keySize!: number;
22+
@t.uint32 public accessor valueSize!: number;
2423

2524
public get name(): string {
26-
return decodeUTF8(this._name).replace(/\0/g, '');
25+
return decodeUTF8(this.subarray(8, 8 + this.keySize));
2726
}
2827

28+
/**
29+
* Note that this does not handle moving the data.
30+
* Changing the name after setting the value is undefined behavior and will lead to corruption.
31+
* This should only be used when creating a new attribute.
32+
*/
2933
public set name(value: string) {
30-
this._name = encodeUTF8(value);
31-
this.keySize = this._name.length;
34+
const buf = encodeUTF8(value);
35+
if (8 + buf.length + this.valueSize > maxDynamicData) throw withErrno('EOVERFLOW');
36+
this.set(buf, 8);
37+
this.keySize = buf.length;
3238
}
3339

34-
@t.uint8(maxAttrValueSize, { countedBy: 'valueSize' }) protected accessor _value!: Uint8Array;
35-
3640
public get value(): Uint8Array {
37-
return this._value;
41+
return this.subarray(8 + this.keySize, this.size);
3842
}
3943

4044
public set value(value: Uint8Array) {
41-
this._value = value;
42-
this.valueSize = Math.min(value.length, maxAttrValueSize);
45+
if (8 + this.keySize + value.length > maxDynamicData) throw withErrno('EOVERFLOW');
46+
this.valueSize = value.length;
47+
this.set(value, 8 + this.keySize);
48+
}
49+
50+
public get size(): number {
51+
return 8 + this.keySize + this.valueSize;
4352
}
4453
}
4554

@@ -49,52 +58,117 @@ class Attribute extends BufferView {
4958
* @internal
5059
*/
5160
@struct(packed)
52-
export class Attributes extends BufferView {
61+
export class Attributes<T extends ArrayBufferLike = ArrayBufferLike> implements ArrayBufferView<T> {
5362
@t.uint32 accessor size!: number;
5463

55-
@field(Attribute, { length: 0, countedBy: 'size' }) accessor data!: Attribute[];
64+
declare ['constructor']: typeof Attributes;
65+
66+
declare readonly buffer: T;
67+
declare readonly byteOffset: number;
68+
declare readonly byteLength: number;
69+
constructor(buffer?: T | ArrayBufferView<T> | ArrayLike<number> | number, byteOffset?: number, byteLength?: number) {
70+
initView(this, buffer, byteOffset, byteLength);
71+
}
72+
73+
public get byteSize(): number {
74+
let offset = this.byteOffset + sizeof(this);
75+
for (let i = 0; i < this.size; i++) {
76+
const entry = new Attribute(this.buffer, offset);
77+
offset += entry.size;
78+
}
79+
return offset;
80+
}
5681

5782
public has(name: string): boolean {
58-
return this.data.some(entry => entry.name == name);
83+
let offset = this.byteOffset + sizeof(this);
84+
for (let i = 0; i < this.size; i++) {
85+
const entry = new Attribute(this.buffer, offset);
86+
if (entry.name == name) return true;
87+
offset += entry.size;
88+
}
89+
return false;
5990
}
6091

61-
public get(name: string): Attribute | undefined {
62-
return this.data.find(entry => entry.name == name);
92+
public get(name: string): Uint8Array | undefined {
93+
let offset = this.byteOffset + sizeof(this);
94+
for (let i = 0; i < this.size; i++) {
95+
const entry = new Attribute(this.buffer, offset);
96+
if (entry.name == name) return entry.value;
97+
//if (entry.name == name) return new Uint8Array(this.buffer, offset, entry.valueSize);
98+
offset += entry.size;
99+
}
63100
}
64101

65102
public set(name: string, value: Uint8Array): void {
66-
const attr = this.get(name);
103+
let offset = this.byteOffset + sizeof(this);
104+
let remove;
105+
for (let i = 0; i < this.size; i++) {
106+
const entry = new Attribute(this.buffer, offset);
107+
if (entry.name == name) remove = [offset, entry.size];
108+
offset += entry.size;
109+
}
67110

68-
if (attr) {
69-
attr.value = value;
70-
return;
111+
const buf = new Uint8Array(this.buffer);
112+
113+
if (remove) {
114+
const [start, size] = remove;
115+
offset -= size;
116+
buf.copyWithin(start, start + size, offset + size);
117+
buf.fill(0, offset, offset + size);
118+
this.size--;
71119
}
72120

73-
const new_attr = new Attribute();
74-
new_attr.name = name;
75-
new_attr.value = value;
76-
this.data.push(new_attr);
121+
const attr = new Attribute(this.buffer, offset);
122+
attr.name = name;
123+
attr.value = value;
77124
this.size++;
78125
}
79126

80127
public remove(name: string): boolean {
81-
const index = this.data.findIndex(entry => entry.name == name);
82-
if (index === -1) return false;
83-
this.data.splice(index, 1);
128+
let offset = this.byteOffset + sizeof(this);
129+
let remove;
130+
for (let i = 0; i < this.size; i++) {
131+
const entry = new Attribute(this.buffer, offset);
132+
if (entry.name == name) remove = [offset, entry.size];
133+
offset += entry.size;
134+
}
135+
136+
if (!remove) return false;
137+
138+
const [start, size] = remove;
139+
const buf = new Uint8Array(this.buffer);
140+
buf.copyWithin(start, start + size, offset);
141+
buf.fill(0, offset - size, offset);
142+
84143
this.size--;
85144
return true;
86145
}
87146

88-
public keys(): string[] {
89-
return this.data.map(entry => entry.name);
147+
public *keys() {
148+
let offset = this.byteOffset + sizeof(this);
149+
for (let i = 0; i < this.size; i++) {
150+
const entry = new Attribute(this.buffer, offset);
151+
yield entry.name;
152+
offset += entry.size;
153+
}
90154
}
91155

92-
public values(): Uint8Array[] {
93-
return this.data.map(entry => entry.value);
156+
public *values() {
157+
let offset = this.byteOffset + sizeof(this);
158+
for (let i = 0; i < this.size; i++) {
159+
const entry = new Attribute(this.buffer, offset);
160+
yield entry.value;
161+
offset += entry.size;
162+
}
94163
}
95164

96-
public entries(): [string, Uint8Array][] {
97-
return this.data.map(entry => [entry.name, entry.value]);
165+
public *entries() {
166+
let offset = this.byteOffset + sizeof(this);
167+
for (let i = 0; i < this.size; i++) {
168+
const entry = new Attribute(this.buffer, offset);
169+
yield [entry.name, entry.value];
170+
offset += entry.size;
171+
}
98172
}
99173
}
100174

@@ -139,10 +213,11 @@ export const _inode_fields = [
139213
* 1. 58 bytes. The first member was called `ino` but used as the ID for data.
140214
* 2. 66 bytes. Renamed the first member from `ino` to `data` and added a separate `ino` field
141215
* 3. 72 bytes. Changed the ID fields from 64 to 32 bits and added `flags`.
142-
* 4. (current) Added extended attributes. At least 128 bytes.
216+
* 4. >= 128 bytes. Added extended attributes.
217+
* 5. (current) 4 KiB. Changed to a fixed size to make a lot of size-related stuff easier.
143218
* @internal @hidden
144219
*/
145-
export const _inode_version = 4;
220+
export const _inode_version = 5;
146221

147222
/**
148223
* Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS)
@@ -282,16 +357,26 @@ export class Inode extends BufferView implements InodeLike {
282357

283358
/**
284359
* The "version" of the inode/data.
285-
*
286360
* Unrelated to the inode format!
287361
*/
288362
@t.uint32 accessor version!: number;
289363

290-
/** Pad to 128 bytes */
364+
/**
365+
* Padding up to 128 bytes.
366+
* This ensures there is enough room for expansion without breaking the ABI.
367+
* @internal
368+
*/
291369
@t.uint8(48) protected accessor __padding!: Uint8Array;
292370

293371
@field(Attributes) accessor attributes!: Attributes;
294372

373+
/**
374+
* Since the attribute data uses dynamic arrays,
375+
* it is necessary to add this so attributes can be added.
376+
* @internal @hidden
377+
*/
378+
@t.uint8(maxDynamicData) protected accessor __data!: Uint8Array;
379+
295380
public toString(): string {
296381
return `<Inode ${this.ino}>`;
297382
}

src/vfs/acl.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,11 @@ export function check($: V_Context, inode: InodeLike, access: number): boolean {
143143

144144
const { euid, egid } = $?.credentials ?? defaultContext.credentials;
145145

146-
const attr = inode.attributes.get('system.posix_acl_access');
146+
const data = inode.attributes.get('system.posix_acl_access');
147147

148-
if (!attr) return true;
148+
if (!data) return true;
149149

150-
const acl = new ACL(attr.value);
150+
const acl = new ACL(data);
151151

152152
let mask = R_OK | W_OK | X_OK;
153153

src/vfs/xattr.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ export async function get(this: V_Context, path: string, name: Name, opt: Option
9696

9797
inode.attributes ??= new Attributes();
9898

99-
const attr = inode.attributes.get(name);
100-
if (!attr) throw UV('ENODATA', 'xattr.get', path);
99+
const value = inode.attributes.get(name);
100+
if (!value) throw UV('ENODATA', 'xattr.get', path);
101101

102-
const buffer = Buffer.from(attr.value);
102+
const buffer = Buffer.from(value);
103103

104104
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
105105
}
@@ -130,10 +130,10 @@ export function getSync(this: V_Context, path: string, name: Name, opt: Options
130130

131131
inode.attributes ??= new Attributes();
132132

133-
const attr = inode.attributes.get(name);
134-
if (!attr) throw UV('ENODATA', 'xattr.get', path);
133+
const value = inode.attributes.get(name);
134+
if (!value) throw UV('ENODATA', 'xattr.get', path);
135135

136-
const buffer = Buffer.from(attr.value);
136+
const buffer = Buffer.from(value);
137137

138138
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
139139
}
@@ -281,7 +281,7 @@ export async function list(this: V_Context, path: string): Promise<Name[]> {
281281

282282
if (!inode.attributes) return [];
283283

284-
return inode.attributes.keys() as Name[];
284+
return inode.attributes.keys().toArray() as Name[];
285285
}
286286

287287
/**
@@ -303,5 +303,5 @@ export function listSync(this: V_Context, path: string): Name[] {
303303

304304
if (!inode.attributes) return [];
305305

306-
return inode.attributes.keys() as Name[];
306+
return inode.attributes.keys().toArray() as Name[];
307307
}

0 commit comments

Comments
 (0)