@ecmaos/coreutils 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/dist/commands/awk.d.ts +4 -0
  4. package/dist/commands/awk.d.ts.map +1 -0
  5. package/dist/commands/awk.js +324 -0
  6. package/dist/commands/awk.js.map +1 -0
  7. package/dist/commands/chgrp.d.ts +4 -0
  8. package/dist/commands/chgrp.d.ts.map +1 -0
  9. package/dist/commands/chgrp.js +187 -0
  10. package/dist/commands/chgrp.js.map +1 -0
  11. package/dist/commands/chmod.d.ts.map +1 -1
  12. package/dist/commands/chmod.js +139 -2
  13. package/dist/commands/chmod.js.map +1 -1
  14. package/dist/commands/chown.d.ts +4 -0
  15. package/dist/commands/chown.d.ts.map +1 -0
  16. package/dist/commands/chown.js +257 -0
  17. package/dist/commands/chown.js.map +1 -0
  18. package/dist/commands/cksum.d.ts +4 -0
  19. package/dist/commands/cksum.d.ts.map +1 -0
  20. package/dist/commands/cksum.js +124 -0
  21. package/dist/commands/cksum.js.map +1 -0
  22. package/dist/commands/cmp.d.ts +4 -0
  23. package/dist/commands/cmp.d.ts.map +1 -0
  24. package/dist/commands/cmp.js +120 -0
  25. package/dist/commands/cmp.js.map +1 -0
  26. package/dist/commands/column.d.ts +4 -0
  27. package/dist/commands/column.d.ts.map +1 -0
  28. package/dist/commands/column.js +274 -0
  29. package/dist/commands/column.js.map +1 -0
  30. package/dist/commands/cp.d.ts.map +1 -1
  31. package/dist/commands/cp.js +81 -4
  32. package/dist/commands/cp.js.map +1 -1
  33. package/dist/commands/cron.d.ts.map +1 -1
  34. package/dist/commands/cron.js +116 -23
  35. package/dist/commands/cron.js.map +1 -1
  36. package/dist/commands/curl.d.ts +4 -0
  37. package/dist/commands/curl.d.ts.map +1 -0
  38. package/dist/commands/curl.js +238 -0
  39. package/dist/commands/curl.js.map +1 -0
  40. package/dist/commands/du.d.ts +4 -0
  41. package/dist/commands/du.d.ts.map +1 -0
  42. package/dist/commands/du.js +168 -0
  43. package/dist/commands/du.js.map +1 -0
  44. package/dist/commands/echo.d.ts.map +1 -1
  45. package/dist/commands/echo.js +125 -2
  46. package/dist/commands/echo.js.map +1 -1
  47. package/dist/commands/env.d.ts +4 -0
  48. package/dist/commands/env.d.ts.map +1 -0
  49. package/dist/commands/env.js +129 -0
  50. package/dist/commands/env.js.map +1 -0
  51. package/dist/commands/expand.d.ts +4 -0
  52. package/dist/commands/expand.d.ts.map +1 -0
  53. package/dist/commands/expand.js +197 -0
  54. package/dist/commands/expand.js.map +1 -0
  55. package/dist/commands/factor.d.ts +4 -0
  56. package/dist/commands/factor.d.ts.map +1 -0
  57. package/dist/commands/factor.js +141 -0
  58. package/dist/commands/factor.js.map +1 -0
  59. package/dist/commands/fmt.d.ts +4 -0
  60. package/dist/commands/fmt.d.ts.map +1 -0
  61. package/dist/commands/fmt.js +278 -0
  62. package/dist/commands/fmt.js.map +1 -0
  63. package/dist/commands/fold.d.ts +4 -0
  64. package/dist/commands/fold.d.ts.map +1 -0
  65. package/dist/commands/fold.js +253 -0
  66. package/dist/commands/fold.js.map +1 -0
  67. package/dist/commands/groups.d.ts +4 -0
  68. package/dist/commands/groups.d.ts.map +1 -0
  69. package/dist/commands/groups.js +61 -0
  70. package/dist/commands/groups.js.map +1 -0
  71. package/dist/commands/head.d.ts.map +1 -1
  72. package/dist/commands/head.js +184 -77
  73. package/dist/commands/head.js.map +1 -1
  74. package/dist/commands/hostname.d.ts +4 -0
  75. package/dist/commands/hostname.d.ts.map +1 -0
  76. package/dist/commands/hostname.js +80 -0
  77. package/dist/commands/hostname.js.map +1 -0
  78. package/dist/commands/less.d.ts.map +1 -1
  79. package/dist/commands/less.js +1 -0
  80. package/dist/commands/less.js.map +1 -1
  81. package/dist/commands/man.d.ts.map +1 -1
  82. package/dist/commands/man.js +3 -1
  83. package/dist/commands/man.js.map +1 -1
  84. package/dist/commands/mount.d.ts +4 -0
  85. package/dist/commands/mount.d.ts.map +1 -0
  86. package/dist/commands/mount.js +1136 -0
  87. package/dist/commands/mount.js.map +1 -0
  88. package/dist/commands/od.d.ts +4 -0
  89. package/dist/commands/od.d.ts.map +1 -0
  90. package/dist/commands/od.js +342 -0
  91. package/dist/commands/od.js.map +1 -0
  92. package/dist/commands/pr.d.ts +4 -0
  93. package/dist/commands/pr.d.ts.map +1 -0
  94. package/dist/commands/pr.js +298 -0
  95. package/dist/commands/pr.js.map +1 -0
  96. package/dist/commands/printf.d.ts +4 -0
  97. package/dist/commands/printf.d.ts.map +1 -0
  98. package/dist/commands/printf.js +271 -0
  99. package/dist/commands/printf.js.map +1 -0
  100. package/dist/commands/readlink.d.ts +4 -0
  101. package/dist/commands/readlink.d.ts.map +1 -0
  102. package/dist/commands/readlink.js +104 -0
  103. package/dist/commands/readlink.js.map +1 -0
  104. package/dist/commands/realpath.d.ts +4 -0
  105. package/dist/commands/realpath.d.ts.map +1 -0
  106. package/dist/commands/realpath.js +111 -0
  107. package/dist/commands/realpath.js.map +1 -0
  108. package/dist/commands/rev.d.ts +4 -0
  109. package/dist/commands/rev.d.ts.map +1 -0
  110. package/dist/commands/rev.js +134 -0
  111. package/dist/commands/rev.js.map +1 -0
  112. package/dist/commands/shuf.d.ts +4 -0
  113. package/dist/commands/shuf.d.ts.map +1 -0
  114. package/dist/commands/shuf.js +221 -0
  115. package/dist/commands/shuf.js.map +1 -0
  116. package/dist/commands/sleep.d.ts +4 -0
  117. package/dist/commands/sleep.d.ts.map +1 -0
  118. package/dist/commands/sleep.js +102 -0
  119. package/dist/commands/sleep.js.map +1 -0
  120. package/dist/commands/strings.d.ts +4 -0
  121. package/dist/commands/strings.d.ts.map +1 -0
  122. package/dist/commands/strings.js +170 -0
  123. package/dist/commands/strings.js.map +1 -0
  124. package/dist/commands/tac.d.ts +4 -0
  125. package/dist/commands/tac.d.ts.map +1 -0
  126. package/dist/commands/tac.js +130 -0
  127. package/dist/commands/tac.js.map +1 -0
  128. package/dist/commands/time.d.ts +4 -0
  129. package/dist/commands/time.d.ts.map +1 -0
  130. package/dist/commands/time.js +126 -0
  131. package/dist/commands/time.js.map +1 -0
  132. package/dist/commands/umount.d.ts +4 -0
  133. package/dist/commands/umount.d.ts.map +1 -0
  134. package/dist/commands/umount.js +103 -0
  135. package/dist/commands/umount.js.map +1 -0
  136. package/dist/commands/uname.d.ts +4 -0
  137. package/dist/commands/uname.d.ts.map +1 -0
  138. package/dist/commands/uname.js +149 -0
  139. package/dist/commands/uname.js.map +1 -0
  140. package/dist/commands/unexpand.d.ts +4 -0
  141. package/dist/commands/unexpand.d.ts.map +1 -0
  142. package/dist/commands/unexpand.js +286 -0
  143. package/dist/commands/unexpand.js.map +1 -0
  144. package/dist/commands/uptime.d.ts +4 -0
  145. package/dist/commands/uptime.d.ts.map +1 -0
  146. package/dist/commands/uptime.js +62 -0
  147. package/dist/commands/uptime.js.map +1 -0
  148. package/dist/commands/view.d.ts +1 -0
  149. package/dist/commands/view.d.ts.map +1 -1
  150. package/dist/commands/view.js +408 -66
  151. package/dist/commands/view.js.map +1 -1
  152. package/dist/commands/yes.d.ts +4 -0
  153. package/dist/commands/yes.d.ts.map +1 -0
  154. package/dist/commands/yes.js +58 -0
  155. package/dist/commands/yes.js.map +1 -0
  156. package/dist/index.d.ts +24 -0
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +82 -0
  159. package/dist/index.js.map +1 -1
  160. package/package.json +12 -3
  161. package/src/commands/awk.ts +340 -0
  162. package/src/commands/chmod.ts +141 -2
  163. package/src/commands/chown.ts +321 -0
  164. package/src/commands/cksum.ts +133 -0
  165. package/src/commands/cmp.ts +126 -0
  166. package/src/commands/column.ts +273 -0
  167. package/src/commands/cp.ts +93 -4
  168. package/src/commands/cron.ts +115 -23
  169. package/src/commands/curl.ts +231 -0
  170. package/src/commands/echo.ts +122 -2
  171. package/src/commands/env.ts +143 -0
  172. package/src/commands/expand.ts +207 -0
  173. package/src/commands/factor.ts +151 -0
  174. package/src/commands/fmt.ts +293 -0
  175. package/src/commands/fold.ts +257 -0
  176. package/src/commands/groups.ts +72 -0
  177. package/src/commands/head.ts +176 -77
  178. package/src/commands/hostname.ts +81 -0
  179. package/src/commands/less.ts +1 -0
  180. package/src/commands/man.ts +4 -1
  181. package/src/commands/mount.ts +1302 -0
  182. package/src/commands/od.ts +327 -0
  183. package/src/commands/pr.ts +291 -0
  184. package/src/commands/printf.ts +271 -0
  185. package/src/commands/readlink.ts +102 -0
  186. package/src/commands/realpath.ts +126 -0
  187. package/src/commands/rev.ts +143 -0
  188. package/src/commands/shuf.ts +218 -0
  189. package/src/commands/sleep.ts +109 -0
  190. package/src/commands/strings.ts +176 -0
  191. package/src/commands/tac.ts +138 -0
  192. package/src/commands/time.ts +144 -0
  193. package/src/commands/umount.ts +116 -0
  194. package/src/commands/uname.ts +130 -0
  195. package/src/commands/unexpand.ts +305 -0
  196. package/src/commands/uptime.ts +73 -0
  197. package/src/commands/view.ts +463 -73
  198. package/src/index.ts +82 -0
  199. package/tsconfig.json +4 -0
@@ -0,0 +1,321 @@
1
+ import path from 'path'
2
+ import chalk from 'chalk'
3
+ import type { Kernel, Process, Shell, Terminal, User } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStderr, writelnStdout } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: chown [OPTION]... [OWNER][:[GROUP]] FILE...
9
+ or: chown [OPTION]... :GROUP FILE...
10
+ or: chown [OPTION]... --reference=RFILE FILE...
11
+ Change the owner and/or group of each FILE to OWNER and/or GROUP.
12
+
13
+ -R, --recursive operate on files and directories recursively
14
+ -v, --verbose output a diagnostic for every file processed
15
+ -c, --changes like verbose but report only when a change is made
16
+ --help display this help and exit
17
+
18
+ OWNER and GROUP can be specified as:
19
+ - A numeric user ID (UID) or group ID (GID)
20
+ - A username (resolved to UID)
21
+ - A group name (resolved to GID, if supported)
22
+
23
+ The format OWNER:GROUP means set both owner and group.
24
+ The format OWNER: means set owner and set group to owner's primary group.
25
+ The format :GROUP means set group only (keep current owner).
26
+ The format OWNER.GROUP is also accepted (same as OWNER:GROUP).
27
+
28
+ Examples:
29
+ chown root file Change owner of file to root
30
+ chown root:root file Change owner and group to root
31
+ chown :users file Change group to users (keep owner)
32
+ chown root: file Change owner to root, group to root's primary group
33
+ chown -R root:root /dir Recursively change owner and group
34
+ chown -v user file Verbose output while changing owner
35
+ chown -c user file Report only when changes are made`
36
+ writelnStderr(process, terminal, usage)
37
+ }
38
+
39
+ interface OwnershipSpec {
40
+ owner?: number
41
+ group?: number
42
+ ownerOnly: boolean
43
+ groupOnly: boolean
44
+ setGroupToOwnerPrimary: boolean
45
+ }
46
+
47
+ function parseOwnershipSpec(spec: string, kernel: Kernel): OwnershipSpec {
48
+ const result: OwnershipSpec = {
49
+ ownerOnly: false,
50
+ groupOnly: false,
51
+ setGroupToOwnerPrimary: false
52
+ }
53
+
54
+ if (spec.startsWith(':')) {
55
+ result.groupOnly = true
56
+ const groupSpec = spec.slice(1)
57
+ if (!groupSpec) {
58
+ throw new Error('Invalid ownership spec: missing group after colon')
59
+ }
60
+ result.group = resolveGroup(groupSpec, kernel)
61
+ return result
62
+ }
63
+
64
+ if (spec.endsWith(':')) {
65
+ result.ownerOnly = true
66
+ result.setGroupToOwnerPrimary = true
67
+ const ownerSpec = spec.slice(0, -1)
68
+ if (!ownerSpec) {
69
+ throw new Error('Invalid ownership spec: missing owner before colon')
70
+ }
71
+ result.owner = resolveOwner(ownerSpec, kernel)
72
+ const ownerUser = kernel.users.get(result.owner)
73
+ if (ownerUser) {
74
+ result.group = ownerUser.gid
75
+ }
76
+ return result
77
+ }
78
+
79
+ const colonIndex = spec.indexOf(':')
80
+ const dotIndex = spec.indexOf('.')
81
+
82
+ if (colonIndex === -1 && dotIndex === -1) {
83
+ result.owner = resolveOwner(spec, kernel)
84
+ result.ownerOnly = true
85
+ return result
86
+ }
87
+
88
+ const separatorIndex = colonIndex !== -1 ? colonIndex : dotIndex
89
+ const ownerSpec = spec.slice(0, separatorIndex)
90
+ const groupSpec = spec.slice(separatorIndex + 1)
91
+
92
+ if (!ownerSpec && !groupSpec) {
93
+ throw new Error('Invalid ownership spec: both owner and group are empty')
94
+ }
95
+
96
+ if (ownerSpec) {
97
+ result.owner = resolveOwner(ownerSpec, kernel)
98
+ }
99
+
100
+ if (groupSpec) {
101
+ result.group = resolveGroup(groupSpec, kernel)
102
+ }
103
+
104
+ return result
105
+ }
106
+
107
+ function resolveOwner(ownerSpec: string, kernel: Kernel): number {
108
+ const numericUid = parseInt(ownerSpec, 10)
109
+ if (!isNaN(numericUid) && numericUid.toString() === ownerSpec) {
110
+ return numericUid
111
+ }
112
+
113
+ const user = Array.from(kernel.users.all.values()).find(
114
+ (u): u is User => (u as User).username === ownerSpec
115
+ )
116
+
117
+ if (!user) {
118
+ throw new Error(`Invalid user: ${ownerSpec}`)
119
+ }
120
+
121
+ return user.uid
122
+ }
123
+
124
+ function resolveGroup(groupSpec: string, kernel: Kernel): number {
125
+ const numericGid = parseInt(groupSpec, 10)
126
+ if (!isNaN(numericGid) && numericGid.toString() === groupSpec) {
127
+ return numericGid
128
+ }
129
+
130
+ const user = Array.from(kernel.users.all.values()).find(
131
+ (u): u is User => (u as User).username === groupSpec
132
+ )
133
+
134
+ if (user) {
135
+ return user.gid
136
+ }
137
+
138
+ throw new Error(`Invalid group: ${groupSpec}`)
139
+ }
140
+
141
+ async function getCurrentOwnership(
142
+ fs: typeof import('@zenfs/core').fs.promises,
143
+ filePath: string
144
+ ): Promise<{ uid: number; gid: number }> {
145
+ try {
146
+ const stats = await fs.stat(filePath)
147
+ return { uid: stats.uid, gid: stats.gid }
148
+ } catch (error) {
149
+ throw new Error(`Cannot access ${filePath}: ${error instanceof Error ? error.message : String(error)}`)
150
+ }
151
+ }
152
+
153
+ async function changeOwnership(
154
+ fs: typeof import('@zenfs/core').fs.promises,
155
+ filePath: string,
156
+ spec: OwnershipSpec,
157
+ kernel: Kernel
158
+ ): Promise<{ uid: number; gid: number }> {
159
+ const current = await getCurrentOwnership(fs, filePath)
160
+ let newUid = current.uid
161
+ let newGid = current.gid
162
+
163
+ if (spec.ownerOnly) {
164
+ newUid = spec.owner ?? current.uid
165
+ if (spec.setGroupToOwnerPrimary && spec.owner !== undefined) {
166
+ const ownerUser = kernel.users.get(spec.owner)
167
+ if (ownerUser) {
168
+ newGid = ownerUser.gid
169
+ }
170
+ } else if (spec.group !== undefined) {
171
+ newGid = spec.group
172
+ }
173
+ } else if (spec.groupOnly) {
174
+ newGid = spec.group ?? current.gid
175
+ } else {
176
+ if (spec.owner !== undefined) {
177
+ newUid = spec.owner
178
+ }
179
+ if (spec.group !== undefined) {
180
+ newGid = spec.group
181
+ }
182
+ }
183
+
184
+ await fs.chown(filePath, newUid, newGid)
185
+ return { uid: newUid, gid: newGid }
186
+ }
187
+
188
+ async function processFile(
189
+ fs: typeof import('@zenfs/core').fs.promises,
190
+ filePath: string,
191
+ spec: OwnershipSpec,
192
+ kernel: Kernel,
193
+ options: { recursive: boolean; verbose: boolean; changes: boolean },
194
+ process: Process | undefined,
195
+ terminal: Terminal,
196
+ relativePath: string
197
+ ): Promise<boolean> {
198
+ try {
199
+ const current = await getCurrentOwnership(fs, filePath)
200
+ const newOwnership = await changeOwnership(fs, filePath, spec, kernel)
201
+ const changed = current.uid !== newOwnership.uid || current.gid !== newOwnership.gid
202
+
203
+ if (options.verbose || (options.changes && changed)) {
204
+ const changeInfo = changed
205
+ ? `changed ownership of '${relativePath}' from ${current.uid}:${current.gid} to ${newOwnership.uid}:${newOwnership.gid}`
206
+ : `ownership of '${relativePath}' retained as ${newOwnership.uid}:${newOwnership.gid}`
207
+ await writelnStdout(process, terminal, changeInfo)
208
+ }
209
+
210
+ if (options.recursive) {
211
+ try {
212
+ const stats = await fs.stat(filePath)
213
+ if (stats.isDirectory()) {
214
+ const entries = await fs.readdir(filePath)
215
+ for (const entry of entries) {
216
+ const entryPath = path.join(filePath, entry)
217
+ const entryRelativePath = path.join(relativePath, entry)
218
+ await processFile(fs, entryPath, spec, kernel, options, process, terminal, entryRelativePath)
219
+ }
220
+ }
221
+ } catch (error) {
222
+ const errorMessage = error instanceof Error ? error.message : String(error)
223
+ await writelnStderr(process, terminal, `chown: ${relativePath}: ${errorMessage}`)
224
+ }
225
+ }
226
+
227
+ return false
228
+ } catch (error) {
229
+ const errorMessage = error instanceof Error ? error.message : String(error)
230
+ await writelnStderr(process, terminal, `chown: ${relativePath}: ${errorMessage}`)
231
+ return true
232
+ }
233
+ }
234
+
235
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
236
+ return new TerminalCommand({
237
+ command: 'chown',
238
+ description: 'Change file owner and group',
239
+ kernel,
240
+ shell,
241
+ terminal,
242
+ run: async (pid: number, argv: string[]) => {
243
+ const process = kernel.processes.get(pid) as Process | undefined
244
+
245
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
246
+ printUsage(process, terminal)
247
+ return 0
248
+ }
249
+
250
+ let recursive = false
251
+ let verbose = false
252
+ let changes = false
253
+ const args: string[] = []
254
+
255
+ for (const arg of argv) {
256
+ if (arg === '-R' || arg === '--recursive') {
257
+ recursive = true
258
+ } else if (arg === '-v' || arg === '--verbose') {
259
+ verbose = true
260
+ } else if (arg === '-c' || arg === '--changes') {
261
+ changes = true
262
+ } else if (arg === '--reference') {
263
+ await writelnStderr(process, terminal, chalk.red('chown: --reference option not yet implemented'))
264
+ return 1
265
+ } else if (arg && !arg.startsWith('-')) {
266
+ args.push(arg)
267
+ } else if (arg.startsWith('-')) {
268
+ await writelnStderr(process, terminal, chalk.red(`chown: invalid option '${arg}'`))
269
+ await writelnStderr(process, terminal, 'Try \'chown --help\' for more information.')
270
+ return 1
271
+ }
272
+ }
273
+
274
+ if (args.length === 0) {
275
+ await writelnStderr(process, terminal, chalk.red('chown: missing operand'))
276
+ await writelnStderr(process, terminal, 'Try \'chown --help\' for more information.')
277
+ return 1
278
+ }
279
+
280
+ const ownershipSpec = args[0]
281
+ const targets = args.slice(1)
282
+
283
+ if (!ownershipSpec || targets.length === 0) {
284
+ await writelnStderr(process, terminal, chalk.red('chown: missing operand'))
285
+ await writelnStderr(process, terminal, 'Try \'chown --help\' for more information.')
286
+ return 1
287
+ }
288
+
289
+ let spec: OwnershipSpec
290
+ try {
291
+ spec = parseOwnershipSpec(ownershipSpec, kernel)
292
+ } catch (error) {
293
+ const errorMessage = error instanceof Error ? error.message : String(error)
294
+ await writelnStderr(process, terminal, chalk.red(`chown: invalid ownership spec '${ownershipSpec}': ${errorMessage}`))
295
+ return 1
296
+ }
297
+
298
+ let hasError = false
299
+ const options = { recursive, verbose, changes }
300
+
301
+ for (const target of targets) {
302
+ const fullPath = path.resolve(shell.cwd, target)
303
+ const error = await processFile(
304
+ shell.context.fs.promises,
305
+ fullPath,
306
+ spec,
307
+ kernel,
308
+ options,
309
+ process,
310
+ terminal,
311
+ target
312
+ )
313
+ if (error) {
314
+ hasError = true
315
+ }
316
+ }
317
+
318
+ return hasError ? 1 : 0
319
+ }
320
+ })
321
+ }
@@ -0,0 +1,133 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: cksum [FILE]...
8
+ Print CRC checksum and byte count for each FILE.
9
+
10
+ If no FILE is specified, or if FILE is -, read standard input.
11
+
12
+ --help display this help and exit`
13
+ writelnStderr(process, terminal, usage)
14
+ }
15
+
16
+ function calculateCRC32(data: Uint8Array): number {
17
+ let crc = 0xffffffff
18
+ const polynomial = 0xedb88320
19
+
20
+ for (let i = 0; i < data.length; i++) {
21
+ const byte = data[i]
22
+ if (byte === undefined) continue
23
+ crc ^= byte
24
+ for (let j = 0; j < 8; j++) {
25
+ if (crc & 1) {
26
+ crc = (crc >>> 1) ^ polynomial
27
+ } else {
28
+ crc = crc >>> 1
29
+ }
30
+ }
31
+ }
32
+
33
+ return (crc ^ 0xffffffff) >>> 0
34
+ }
35
+
36
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
37
+ return new TerminalCommand({
38
+ command: 'cksum',
39
+ description: 'Print CRC checksum and byte count',
40
+ kernel,
41
+ shell,
42
+ terminal,
43
+ run: async (pid: number, argv: string[]) => {
44
+ const process = kernel.processes.get(pid) as Process | undefined
45
+
46
+ if (!process) return 1
47
+
48
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
49
+ printUsage(process, terminal)
50
+ return 0
51
+ }
52
+
53
+ const files: string[] = []
54
+
55
+ for (const arg of argv) {
56
+ if (!arg) continue
57
+
58
+ if (arg === '--help' || arg === '-h') {
59
+ printUsage(process, terminal)
60
+ return 0
61
+ } else if (!arg.startsWith('-')) {
62
+ files.push(arg)
63
+ } else {
64
+ await writelnStderr(process, terminal, `cksum: invalid option -- '${arg.slice(1)}'`)
65
+ await writelnStderr(process, terminal, "Try 'cksum --help' for more information.")
66
+ return 1
67
+ }
68
+ }
69
+
70
+ const writer = process.stdout.getWriter()
71
+
72
+ try {
73
+ if (files.length === 0) {
74
+ if (!process.stdin) {
75
+ return 0
76
+ }
77
+
78
+ const reader = process.stdin.getReader()
79
+ const chunks: Uint8Array[] = []
80
+
81
+ try {
82
+ while (true) {
83
+ const { done, value } = await reader.read()
84
+ if (done) break
85
+ if (value) {
86
+ chunks.push(value)
87
+ }
88
+ }
89
+ } finally {
90
+ reader.releaseLock()
91
+ }
92
+
93
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
94
+ const data = new Uint8Array(totalLength)
95
+ let offset = 0
96
+ for (const chunk of chunks) {
97
+ data.set(chunk, offset)
98
+ offset += chunk.length
99
+ }
100
+
101
+ const crc = calculateCRC32(data)
102
+ await writer.write(new TextEncoder().encode(`${crc} ${data.length}\n`))
103
+ return 0
104
+ }
105
+
106
+ for (const file of files) {
107
+ const fullPath = path.resolve(shell.cwd, file)
108
+
109
+ try {
110
+ if (fullPath.startsWith('/dev')) {
111
+ await writelnStderr(process, terminal, `cksum: ${file}: cannot checksum device files`)
112
+ continue
113
+ }
114
+
115
+ const data = await shell.context.fs.promises.readFile(fullPath)
116
+ const bytes = new Uint8Array(data)
117
+ const crc = calculateCRC32(bytes)
118
+ await writer.write(new TextEncoder().encode(`${crc} ${bytes.length} ${file}\n`))
119
+ } catch (error) {
120
+ await writelnStderr(process, terminal, `cksum: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
121
+ }
122
+ }
123
+
124
+ return 0
125
+ } catch (error) {
126
+ await writelnStderr(process, terminal, `cksum: ${error instanceof Error ? error.message : 'Unknown error'}`)
127
+ return 1
128
+ } finally {
129
+ writer.releaseLock()
130
+ }
131
+ }
132
+ })
133
+ }
@@ -0,0 +1,126 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalEvents } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: cmp [OPTION]... FILE1 FILE2
9
+ Compare two files byte by byte.
10
+
11
+ -l, --verbose print byte number and differing byte values
12
+ -s, --quiet, --silent suppress output; return exit status only
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
17
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
18
+ return new TerminalCommand({
19
+ command: 'cmp',
20
+ description: 'Compare two files byte by byte',
21
+ kernel,
22
+ shell,
23
+ terminal,
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
27
+ if (!process) return 1
28
+
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
34
+ let verbose = false
35
+ let quiet = false
36
+ const files: string[] = []
37
+
38
+ for (const arg of argv) {
39
+ if (!arg) continue
40
+
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (arg === '-l' || arg === '--verbose') {
45
+ verbose = true
46
+ } else if (arg === '-s' || arg === '--quiet' || arg === '--silent') {
47
+ quiet = true
48
+ } else if (arg.startsWith('-')) {
49
+ const flags = arg.slice(1).split('')
50
+ if (flags.includes('l')) verbose = true
51
+ if (flags.includes('s')) quiet = true
52
+ const invalidFlags = flags.filter(f => !['l', 's'].includes(f))
53
+ if (invalidFlags.length > 0) {
54
+ await writelnStderr(process, terminal, `cmp: invalid option -- '${invalidFlags[0]}'`)
55
+ await writelnStderr(process, terminal, "Try 'cmp --help' for more information.")
56
+ return 1
57
+ }
58
+ } else {
59
+ files.push(arg)
60
+ }
61
+ }
62
+
63
+ if (files.length !== 2) {
64
+ await writelnStderr(process, terminal, 'cmp: missing operand after')
65
+ await writelnStderr(process, terminal, "Try 'cmp --help' for more information.")
66
+ return 1
67
+ }
68
+
69
+ const file1 = files[0]
70
+ const file2 = files[1]
71
+ if (!file1 || !file2) {
72
+ await writelnStderr(process, terminal, 'cmp: missing operand')
73
+ return 1
74
+ }
75
+
76
+ const fullPath1 = path.resolve(shell.cwd, file1)
77
+ const fullPath2 = path.resolve(shell.cwd, file2)
78
+
79
+ let interrupted = false
80
+ const interruptHandler = () => { interrupted = true }
81
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
82
+
83
+ try {
84
+ if (fullPath1.startsWith('/dev') || fullPath2.startsWith('/dev')) {
85
+ await writelnStderr(process, terminal, 'cmp: cannot compare device files')
86
+ return 1
87
+ }
88
+
89
+ const data1 = await shell.context.fs.promises.readFile(fullPath1)
90
+ const data2 = await shell.context.fs.promises.readFile(fullPath2)
91
+
92
+ const bytes1 = new Uint8Array(data1)
93
+ const bytes2 = new Uint8Array(data2)
94
+
95
+ const minLength = Math.min(bytes1.length, bytes2.length)
96
+
97
+ for (let i = 0; i < minLength; i++) {
98
+ if (interrupted) break
99
+ if (bytes1[i] !== bytes2[i]) {
100
+ if (verbose) {
101
+ await writelnStderr(process, terminal, `${i + 1} ${bytes1[i]} ${bytes2[i]}`)
102
+ } else if (!quiet) {
103
+ await writelnStderr(process, terminal, `${file1} ${file2} differ: byte ${i + 1}, line ${Math.floor(i / 80) + 1}`)
104
+ }
105
+ return 1
106
+ }
107
+ }
108
+
109
+ if (bytes1.length !== bytes2.length) {
110
+ if (!quiet) {
111
+ await writelnStderr(process, terminal, `cmp: EOF on ${bytes1.length < bytes2.length ? file1 : file2}`)
112
+ }
113
+ return 1
114
+ }
115
+
116
+ return 0
117
+ } catch (error) {
118
+ const errorMessage = error instanceof Error ? error.message : String(error)
119
+ await writelnStderr(process, terminal, `cmp: ${errorMessage}`)
120
+ return 1
121
+ } finally {
122
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
123
+ }
124
+ }
125
+ })
126
+ }