@@ -2,7 +2,7 @@ import { withErrno } from 'kerium';
2
2
import { crit , warn } from 'kerium/log' ;
3
3
import { field , packed , sizeof , struct , types as t , type Struct } from 'memium' ;
4
4
import { decodeUTF8 , encodeUTF8 , pick } from 'utilium' ;
5
- import { BufferView } from 'utilium/buffer.js' ;
5
+ import { BufferView , initView } from 'utilium/buffer.js' ;
6
6
import * as c from '../vfs/constants.js' ;
7
7
import { Stats , type StatsLike } from '../vfs/stats.js' ;
8
8
import { defaultContext , type V_Context } from './contexts.js' ;
@@ -13,33 +13,42 @@ import { defaultContext, type V_Context } from './contexts.js';
13
13
*/
14
14
export const rootIno = 0 ;
15
15
16
- const maxAttrValueSize = 1024 ;
16
+ /** 4 KiB minus static inode data */
17
+ const maxDynamicData = 3968 ;
17
18
18
19
@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 ;
24
23
25
24
public get name ( ) : string {
26
- return decodeUTF8 ( this . _name ) . replace ( / \0 / g , '' ) ;
25
+ return decodeUTF8 ( this . subarray ( 8 , 8 + this . keySize ) ) ;
27
26
}
28
27
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
+ */
29
33
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 ;
32
38
}
33
39
34
- @t . uint8 ( maxAttrValueSize , { countedBy : 'valueSize' } ) protected accessor _value ! : Uint8Array ;
35
-
36
40
public get value ( ) : Uint8Array {
37
- return this . _value ;
41
+ return this . subarray ( 8 + this . keySize , this . size ) ;
38
42
}
39
43
40
44
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 ;
43
52
}
44
53
}
45
54
@@ -49,52 +58,117 @@ class Attribute extends BufferView {
49
58
* @internal
50
59
*/
51
60
@struct ( packed )
52
- export class Attributes extends BufferView {
61
+ export class Attributes < T extends ArrayBufferLike = ArrayBufferLike > implements ArrayBufferView < T > {
53
62
@t . uint32 accessor size ! : number ;
54
63
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
+ }
56
81
57
82
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 ;
59
90
}
60
91
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
+ }
63
100
}
64
101
65
102
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
+ }
67
110
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 -- ;
71
119
}
72
120
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 ;
77
124
this . size ++ ;
78
125
}
79
126
80
127
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
+
84
143
this . size -- ;
85
144
return true ;
86
145
}
87
146
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
+ }
90
154
}
91
155
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
+ }
94
163
}
95
164
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
+ }
98
172
}
99
173
}
100
174
@@ -139,10 +213,11 @@ export const _inode_fields = [
139
213
* 1. 58 bytes. The first member was called `ino` but used as the ID for data.
140
214
* 2. 66 bytes. Renamed the first member from `ino` to `data` and added a separate `ino` field
141
215
* 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.
143
218
* @internal @hidden
144
219
*/
145
- export const _inode_version = 4 ;
220
+ export const _inode_version = 5 ;
146
221
147
222
/**
148
223
* Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS)
@@ -282,16 +357,26 @@ export class Inode extends BufferView implements InodeLike {
282
357
283
358
/**
284
359
* The "version" of the inode/data.
285
- *
286
360
* Unrelated to the inode format!
287
361
*/
288
362
@t . uint32 accessor version ! : number ;
289
363
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
+ */
291
369
@t . uint8 ( 48 ) protected accessor __padding ! : Uint8Array ;
292
370
293
371
@field ( Attributes ) accessor attributes ! : Attributes ;
294
372
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
+
295
380
public toString ( ) : string {
296
381
return `<Inode ${ this . ino } >` ;
297
382
}
0 commit comments