@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +48 -0
- package/dist/commands/awk.d.ts +4 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +324 -0
- package/dist/commands/awk.js.map +1 -0
- package/dist/commands/chgrp.d.ts +4 -0
- package/dist/commands/chgrp.d.ts.map +1 -0
- package/dist/commands/chgrp.js +187 -0
- package/dist/commands/chgrp.js.map +1 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +139 -2
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/chown.d.ts +4 -0
- package/dist/commands/chown.d.ts.map +1 -0
- package/dist/commands/chown.js +257 -0
- package/dist/commands/chown.js.map +1 -0
- package/dist/commands/cksum.d.ts +4 -0
- package/dist/commands/cksum.d.ts.map +1 -0
- package/dist/commands/cksum.js +124 -0
- package/dist/commands/cksum.js.map +1 -0
- package/dist/commands/cmp.d.ts +4 -0
- package/dist/commands/cmp.d.ts.map +1 -0
- package/dist/commands/cmp.js +120 -0
- package/dist/commands/cmp.js.map +1 -0
- package/dist/commands/column.d.ts +4 -0
- package/dist/commands/column.d.ts.map +1 -0
- package/dist/commands/column.js +274 -0
- package/dist/commands/column.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +81 -4
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cron.d.ts.map +1 -1
- package/dist/commands/cron.js +116 -23
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/curl.d.ts +4 -0
- package/dist/commands/curl.d.ts.map +1 -0
- package/dist/commands/curl.js +238 -0
- package/dist/commands/curl.js.map +1 -0
- package/dist/commands/du.d.ts +4 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +168 -0
- package/dist/commands/du.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +125 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/expand.d.ts +4 -0
- package/dist/commands/expand.d.ts.map +1 -0
- package/dist/commands/expand.js +197 -0
- package/dist/commands/expand.js.map +1 -0
- package/dist/commands/factor.d.ts +4 -0
- package/dist/commands/factor.d.ts.map +1 -0
- package/dist/commands/factor.js +141 -0
- package/dist/commands/factor.js.map +1 -0
- package/dist/commands/fmt.d.ts +4 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +278 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/fold.d.ts +4 -0
- package/dist/commands/fold.d.ts.map +1 -0
- package/dist/commands/fold.js +253 -0
- package/dist/commands/fold.js.map +1 -0
- package/dist/commands/groups.d.ts +4 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +61 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +184 -77
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hostname.d.ts +4 -0
- package/dist/commands/hostname.d.ts.map +1 -0
- package/dist/commands/hostname.js +80 -0
- package/dist/commands/hostname.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +1 -0
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +3 -1
- package/dist/commands/man.js.map +1 -1
- package/dist/commands/mount.d.ts +4 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +1136 -0
- package/dist/commands/mount.js.map +1 -0
- package/dist/commands/od.d.ts +4 -0
- package/dist/commands/od.d.ts.map +1 -0
- package/dist/commands/od.js +342 -0
- package/dist/commands/od.js.map +1 -0
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +298 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/printf.d.ts +4 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +271 -0
- package/dist/commands/printf.js.map +1 -0
- package/dist/commands/readlink.d.ts +4 -0
- package/dist/commands/readlink.d.ts.map +1 -0
- package/dist/commands/readlink.js +104 -0
- package/dist/commands/readlink.js.map +1 -0
- package/dist/commands/realpath.d.ts +4 -0
- package/dist/commands/realpath.d.ts.map +1 -0
- package/dist/commands/realpath.js +111 -0
- package/dist/commands/realpath.js.map +1 -0
- package/dist/commands/rev.d.ts +4 -0
- package/dist/commands/rev.d.ts.map +1 -0
- package/dist/commands/rev.js +134 -0
- package/dist/commands/rev.js.map +1 -0
- package/dist/commands/shuf.d.ts +4 -0
- package/dist/commands/shuf.d.ts.map +1 -0
- package/dist/commands/shuf.js +221 -0
- package/dist/commands/shuf.js.map +1 -0
- package/dist/commands/sleep.d.ts +4 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +102 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/strings.d.ts +4 -0
- package/dist/commands/strings.d.ts.map +1 -0
- package/dist/commands/strings.js +170 -0
- package/dist/commands/strings.js.map +1 -0
- package/dist/commands/tac.d.ts +4 -0
- package/dist/commands/tac.d.ts.map +1 -0
- package/dist/commands/tac.js +130 -0
- package/dist/commands/tac.js.map +1 -0
- package/dist/commands/time.d.ts +4 -0
- package/dist/commands/time.d.ts.map +1 -0
- package/dist/commands/time.js +126 -0
- package/dist/commands/time.js.map +1 -0
- package/dist/commands/umount.d.ts +4 -0
- package/dist/commands/umount.d.ts.map +1 -0
- package/dist/commands/umount.js +103 -0
- package/dist/commands/umount.js.map +1 -0
- package/dist/commands/uname.d.ts +4 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +149 -0
- package/dist/commands/uname.js.map +1 -0
- package/dist/commands/unexpand.d.ts +4 -0
- package/dist/commands/unexpand.d.ts.map +1 -0
- package/dist/commands/unexpand.js +286 -0
- package/dist/commands/unexpand.js.map +1 -0
- package/dist/commands/uptime.d.ts +4 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +62 -0
- package/dist/commands/uptime.js.map +1 -0
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +408 -66
- package/dist/commands/view.js.map +1 -1
- package/dist/commands/yes.d.ts +4 -0
- package/dist/commands/yes.d.ts.map +1 -0
- package/dist/commands/yes.js +58 -0
- package/dist/commands/yes.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
- package/src/commands/awk.ts +340 -0
- package/src/commands/chmod.ts +141 -2
- package/src/commands/chown.ts +321 -0
- package/src/commands/cksum.ts +133 -0
- package/src/commands/cmp.ts +126 -0
- package/src/commands/column.ts +273 -0
- package/src/commands/cp.ts +93 -4
- package/src/commands/cron.ts +115 -23
- package/src/commands/curl.ts +231 -0
- package/src/commands/echo.ts +122 -2
- package/src/commands/env.ts +143 -0
- package/src/commands/expand.ts +207 -0
- package/src/commands/factor.ts +151 -0
- package/src/commands/fmt.ts +293 -0
- package/src/commands/fold.ts +257 -0
- package/src/commands/groups.ts +72 -0
- package/src/commands/head.ts +176 -77
- package/src/commands/hostname.ts +81 -0
- package/src/commands/less.ts +1 -0
- package/src/commands/man.ts +4 -1
- package/src/commands/mount.ts +1302 -0
- package/src/commands/od.ts +327 -0
- package/src/commands/pr.ts +291 -0
- package/src/commands/printf.ts +271 -0
- package/src/commands/readlink.ts +102 -0
- package/src/commands/realpath.ts +126 -0
- package/src/commands/rev.ts +143 -0
- package/src/commands/shuf.ts +218 -0
- package/src/commands/sleep.ts +109 -0
- package/src/commands/strings.ts +176 -0
- package/src/commands/tac.ts +138 -0
- package/src/commands/time.ts +144 -0
- package/src/commands/umount.ts +116 -0
- package/src/commands/uname.ts +130 -0
- package/src/commands/unexpand.ts +305 -0
- package/src/commands/uptime.ts +73 -0
- package/src/commands/view.ts +463 -73
- package/src/index.ts +82 -0
- 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
|
+
}
|