1 | module os |
2 | |
3 | import strings |
4 | |
5 | #include <dirent.h> |
6 | #include <unistd.h> |
7 | #include <fcntl.h> |
8 | #include <sys/utsname.h> |
9 | #include <sys/types.h> |
10 | #include <utime.h> |
11 | |
12 | pub const ( |
13 | path_separator = '/' |
14 | path_delimiter = ':' |
15 | ) |
16 | |
17 | const executable_suffixes = [''] |
18 | |
19 | const ( |
20 | stdin_value = 0 |
21 | stdout_value = 1 |
22 | stderr_value = 2 |
23 | ) |
24 | |
25 | // (Must be realized in Syscall) (Must be specified) |
26 | // ref: http://www.ccfit.nsu.ru/~deviv/courses/unix/unix/ng7c229.html |
27 | pub const ( |
28 | s_ifmt = 0xF000 // type of file |
29 | s_ifdir = 0x4000 // directory |
30 | s_iflnk = 0xa000 // link |
31 | s_isuid = 0o4000 // SUID |
32 | s_isgid = 0o2000 // SGID |
33 | s_isvtx = 0o1000 // Sticky |
34 | s_irusr = 0o0400 // Read by owner |
35 | s_iwusr = 0o0200 // Write by owner |
36 | s_ixusr = 0o0100 // Execute by owner |
37 | s_irgrp = 0o0040 // Read by group |
38 | s_iwgrp = 0o0020 // Write by group |
39 | s_ixgrp = 0o0010 // Execute by group |
40 | s_iroth = 0o0004 // Read by others |
41 | s_iwoth = 0o0002 // Write by others |
42 | s_ixoth = 0o0001 // Execute by others |
43 | ) |
44 | |
45 | fn C.utime(&char, voidptr) int |
46 | |
47 | fn C.uname(name voidptr) int |
48 | |
49 | fn C.symlink(&char, &char) int |
50 | |
51 | fn C.link(&char, &char) int |
52 | |
53 | fn C.gethostname(&char, int) int |
54 | |
55 | // Note: not available on Android fn C.getlogin_r(&char, int) int |
56 | fn C.getlogin() &char |
57 | |
58 | fn C.getppid() int |
59 | |
60 | fn C.getgid() int |
61 | |
62 | fn C.getegid() int |
63 | |
64 | enum GlobMatch { |
65 | exact |
66 | ends_with |
67 | starts_with |
68 | start_and_ends_with |
69 | contains |
70 | any |
71 | } |
72 | |
73 | fn glob_match(dir string, pattern string, next_pattern string, mut matches []string) []string { |
74 | mut subdirs := []string{} |
75 | if is_file(dir) { |
76 | return subdirs |
77 | } |
78 | mut files := ls(dir) or { return subdirs } |
79 | mut mode := GlobMatch.exact |
80 | mut pat := pattern |
81 | if pat == '*' { |
82 | mode = GlobMatch.any |
83 | if next_pattern != pattern && next_pattern != '' { |
84 | for file in files { |
85 | if is_dir('${dir}/${file}') { |
86 | subdirs << '${dir}/${file}' |
87 | } |
88 | } |
89 | return subdirs |
90 | } |
91 | } |
92 | if pat == '**' { |
93 | files = walk_ext(dir, '') |
94 | pat = next_pattern |
95 | } |
96 | if pat.starts_with('*') { |
97 | mode = .ends_with |
98 | pat = pat[1..] |
99 | } |
100 | if pat.ends_with('*') { |
101 | mode = if mode == .ends_with { GlobMatch.contains } else { GlobMatch.starts_with } |
102 | pat = pat[..pat.len - 1] |
103 | } |
104 | if pat.contains('*') { |
105 | mode = .start_and_ends_with |
106 | } |
107 | for file in files { |
108 | mut fpath := file |
109 | f := if file.contains(os.path_separator) { |
110 | pathwalk := file.split(os.path_separator) |
111 | pathwalk[pathwalk.len - 1] |
112 | } else { |
113 | fpath = if dir == '.' { file } else { '${dir}/${file}' } |
114 | file |
115 | } |
116 | if f in ['.', '..'] || f == '' { |
117 | continue |
118 | } |
119 | hit := match mode { |
120 | .any { |
121 | true |
122 | } |
123 | .exact { |
124 | f == pat |
125 | } |
126 | .starts_with { |
127 | f.starts_with(pat) |
128 | } |
129 | .ends_with { |
130 | f.ends_with(pat) |
131 | } |
132 | .start_and_ends_with { |
133 | p := pat.split('*') |
134 | f.starts_with(p[0]) && f.ends_with(p[1]) |
135 | } |
136 | .contains { |
137 | f.contains(pat) |
138 | } |
139 | } |
140 | if hit { |
141 | if is_dir(fpath) { |
142 | subdirs << fpath |
143 | if next_pattern == pattern && next_pattern != '' { |
144 | matches << '${fpath}${os.path_separator}' |
145 | } |
146 | } else { |
147 | matches << fpath |
148 | } |
149 | } |
150 | } |
151 | return subdirs |
152 | } |
153 | |
154 | fn native_glob_pattern(pattern string, mut matches []string) ! { |
155 | steps := pattern.split(os.path_separator) |
156 | mut cwd := if pattern.starts_with(os.path_separator) { os.path_separator } else { '.' } |
157 | mut subdirs := [cwd] |
158 | for i := 0; i < steps.len; i++ { |
159 | step := steps[i] |
160 | step2 := if i + 1 == steps.len { step } else { steps[i + 1] } |
161 | if step == '' { |
162 | continue |
163 | } |
164 | if is_dir('${cwd}${os.path_separator}${step}') { |
165 | dd := if cwd == '/' { |
166 | step |
167 | } else { |
168 | if cwd == '.' || cwd == '' { |
169 | step |
170 | } else { |
171 | if step == '.' || step == '/' { cwd } else { '${cwd}/${step}' } |
172 | } |
173 | } |
174 | if i + 1 != steps.len { |
175 | if dd !in subdirs { |
176 | subdirs << dd |
177 | } |
178 | } |
179 | } |
180 | mut subs := []string{} |
181 | for sd in subdirs { |
182 | d := if cwd == '/' { |
183 | sd |
184 | } else { |
185 | if cwd == '.' || cwd == '' { |
186 | sd |
187 | } else { |
188 | if sd == '.' || sd == '/' { cwd } else { '${cwd}/${sd}' } |
189 | } |
190 | } |
191 | subs << glob_match(d.replace('//', '/'), step, step2, mut matches) |
192 | } |
193 | subdirs = subs.clone() |
194 | } |
195 | } |
196 | |
197 | pub fn utime(path string, actime int, modtime int) ! { |
198 | mut u := C.utimbuf{actime, modtime} |
199 | if C.utime(&char(path.str), voidptr(&u)) != 0 { |
200 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
201 | } |
202 | } |
203 | |
204 | // uname returns information about the platform on which the program is running |
205 | // For example: |
206 | // os.Uname{ |
207 | // sysname: 'Linux' |
208 | // nodename: 'nemesis' |
209 | // release: '5.15.0-57-generic' |
210 | // version: '#63~20.04.1-Ubuntu SMP Wed Nov 30 13:40:16 UTC 2022' |
211 | // machine: 'x86_64' |
212 | // } |
213 | // where the fields have the following meaning: |
214 | // sysname is the name of this implementation of the operating system |
215 | // nodename is the name of this node within an implementation-dependent communications network |
216 | // release is the current release level of this implementation |
217 | // version is the current version level of this release |
218 | // machine is the name of the hardware type, on which the system is running |
219 | // See also https://pubs.opengroup.org/onlinepubs/7908799/xsh/sysutsname.h.html |
220 | pub fn uname() Uname { |
221 | mut u := Uname{} |
222 | utsize := sizeof(C.utsname) |
223 | unsafe { |
224 | x := malloc_noscan(int(utsize)) |
225 | d := &C.utsname(x) |
226 | if C.uname(d) == 0 { |
227 | u.sysname = cstring_to_vstring(d.sysname) |
228 | u.nodename = cstring_to_vstring(d.nodename) |
229 | u.release = cstring_to_vstring(d.release) |
230 | u.version = cstring_to_vstring(d.version) |
231 | u.machine = cstring_to_vstring(d.machine) |
232 | } |
233 | free(d) |
234 | } |
235 | return u |
236 | } |
237 | |
238 | pub fn hostname() !string { |
239 | mut hstnme := '' |
240 | size := 256 |
241 | mut buf := unsafe { &char(malloc_noscan(size)) } |
242 | if C.gethostname(buf, size) == 0 { |
243 | hstnme = unsafe { cstring_to_vstring(buf) } |
244 | unsafe { free(buf) } |
245 | return hstnme |
246 | } |
247 | return error(posix_get_error_msg(C.errno)) |
248 | } |
249 | |
250 | pub fn loginname() !string { |
251 | x := C.getlogin() |
252 | if !isnil(x) { |
253 | return unsafe { cstring_to_vstring(x) } |
254 | } |
255 | return error(posix_get_error_msg(C.errno)) |
256 | } |
257 | |
258 | fn init_os_args(argc int, argv &&u8) []string { |
259 | mut args_ := []string{len: argc} |
260 | for i in 0 .. argc { |
261 | args_[i] = unsafe { tos_clone(argv[i]) } |
262 | } |
263 | return args_ |
264 | } |
265 | |
266 | pub fn ls(path string) ![]string { |
267 | if path.len == 0 { |
268 | return error('ls() expects a folder, not an empty string') |
269 | } |
270 | mut res := []string{cap: 50} |
271 | dir := unsafe { C.opendir(&char(path.str)) } |
272 | if isnil(dir) { |
273 | return error('ls() couldnt open dir "${path}"') |
274 | } |
275 | mut ent := &C.dirent(unsafe { nil }) |
276 | // mut ent := &C.dirent{!} |
277 | for { |
278 | ent = C.readdir(dir) |
279 | if isnil(ent) { |
280 | break |
281 | } |
282 | unsafe { |
283 | bptr := &u8(&ent.d_name[0]) |
284 | if bptr[0] == 0 || (bptr[0] == `.` && bptr[1] == 0) |
285 | || (bptr[0] == `.` && bptr[1] == `.` && bptr[2] == 0) { |
286 | continue |
287 | } |
288 | res << tos_clone(bptr) |
289 | } |
290 | } |
291 | C.closedir(dir) |
292 | return res |
293 | } |
294 | |
295 | // mkdir creates a new directory with the specified path. |
296 | pub fn mkdir(path string, params MkdirParams) ! { |
297 | if path == '.' { |
298 | return |
299 | } |
300 | apath := real_path(path) |
301 | r := unsafe { C.mkdir(&char(apath.str), params.mode) } |
302 | if r == -1 { |
303 | return error(posix_get_error_msg(C.errno)) |
304 | } |
305 | } |
306 | |
307 | // execute starts the specified command, waits for it to complete, and returns its output. |
308 | [manualfree] |
309 | pub fn execute(cmd string) Result { |
310 | // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { |
311 | // return Result{ exit_code: -1, output: ';, &&, || and \\n are not allowed in shell commands' } |
312 | // } |
313 | pcmd := if cmd.contains('2>') { cmd.clone() } else { '${cmd} 2>&1' } |
314 | defer { |
315 | unsafe { pcmd.free() } |
316 | } |
317 | f := vpopen(pcmd) |
318 | if isnil(f) { |
319 | return Result{ |
320 | exit_code: -1 |
321 | output: 'exec("${cmd}") failed' |
322 | } |
323 | } |
324 | fd := fileno(f) |
325 | mut res := strings.new_builder(1024) |
326 | defer { |
327 | unsafe { res.free() } |
328 | } |
329 | buf := [4096]u8{} |
330 | unsafe { |
331 | pbuf := &buf[0] |
332 | for { |
333 | len := C.read(fd, pbuf, 4096) |
334 | if len == 0 { |
335 | break |
336 | } |
337 | res.write_ptr(pbuf, len) |
338 | } |
339 | } |
340 | soutput := res.str() |
341 | exit_code := vpclose(f) |
342 | return Result{ |
343 | exit_code: exit_code |
344 | output: soutput |
345 | } |
346 | } |
347 | |
348 | // raw_execute does the same as `execute` on Unix platforms. |
349 | // On Windows raw_execute starts the specified command, waits for it to complete, and returns its output. |
350 | // It's marked as `unsafe` to help emphasize the problems that may arise by allowing, for example, |
351 | // user provided escape sequences. |
352 | [unsafe] |
353 | pub fn raw_execute(cmd string) Result { |
354 | return execute(cmd) |
355 | } |
356 | |
357 | [manualfree] |
358 | pub fn (mut c Command) start() ! { |
359 | pcmd := c.path + ' 2>&1' |
360 | defer { |
361 | unsafe { pcmd.free() } |
362 | } |
363 | c.f = vpopen(pcmd) |
364 | if isnil(c.f) { |
365 | return error('exec("${c.path}") failed') |
366 | } |
367 | } |
368 | |
369 | [manualfree] |
370 | pub fn (mut c Command) read_line() string { |
371 | buf := [4096]u8{} |
372 | mut res := strings.new_builder(1024) |
373 | defer { |
374 | unsafe { res.free() } |
375 | } |
376 | unsafe { |
377 | bufbp := &buf[0] |
378 | for C.fgets(&char(bufbp), 4096, c.f) != 0 { |
379 | len := vstrlen(bufbp) |
380 | for i in 0 .. len { |
381 | if bufbp[i] == `\n` { |
382 | res.write_ptr(bufbp, i) |
383 | final := res.str() |
384 | return final |
385 | } |
386 | } |
387 | res.write_ptr(bufbp, len) |
388 | } |
389 | } |
390 | c.eof = true |
391 | final := res.str() |
392 | return final |
393 | } |
394 | |
395 | pub fn (mut c Command) close() ! { |
396 | c.exit_code = vpclose(c.f) |
397 | if c.exit_code == 127 { |
398 | return error_with_code('error', 127) |
399 | } |
400 | } |
401 | |
402 | pub fn symlink(origin string, target string) ! { |
403 | res := C.symlink(&char(origin.str), &char(target.str)) |
404 | if res == 0 { |
405 | return |
406 | } |
407 | return error(posix_get_error_msg(C.errno)) |
408 | } |
409 | |
410 | pub fn link(origin string, target string) ! { |
411 | res := C.link(&char(origin.str), &char(target.str)) |
412 | if res == 0 { |
413 | return |
414 | } |
415 | return error(posix_get_error_msg(C.errno)) |
416 | } |
417 | |
418 | // get_error_msg return error code representation in string. |
419 | pub fn get_error_msg(code int) string { |
420 | return posix_get_error_msg(code) |
421 | } |
422 | |
423 | pub fn (mut f File) close() { |
424 | if !f.is_opened { |
425 | return |
426 | } |
427 | f.is_opened = false |
428 | C.fflush(f.cfile) |
429 | C.fclose(f.cfile) |
430 | } |
431 | |
432 | fn C.mkstemp(stemplate &u8) int |
433 | |
434 | // ensure_folder_is_writable checks that `folder` exists, and is writable to the process |
435 | // by creating an empty file in it, then deleting it. |
436 | [manualfree] |
437 | pub fn ensure_folder_is_writable(folder string) ! { |
438 | if !exists(folder) { |
439 | return error_with_code('`${folder}` does not exist', 1) |
440 | } |
441 | if !is_dir(folder) { |
442 | return error_with_code('`${folder}` is not a folder', 2) |
443 | } |
444 | tmp_perm_check := join_path_single(folder, 'XXXXXX') |
445 | defer { |
446 | unsafe { tmp_perm_check.free() } |
447 | } |
448 | unsafe { |
449 | x := C.mkstemp(&char(tmp_perm_check.str)) |
450 | if -1 == x { |
451 | return error_with_code('folder `${folder}` is not writable', 3) |
452 | } |
453 | C.close(x) |
454 | } |
455 | rm(tmp_perm_check)! |
456 | } |
457 | |
458 | [inline] |
459 | pub fn getpid() int { |
460 | return C.getpid() |
461 | } |
462 | |
463 | [inline] |
464 | pub fn getppid() int { |
465 | return C.getppid() |
466 | } |
467 | |
468 | [inline] |
469 | pub fn getuid() int { |
470 | return C.getuid() |
471 | } |
472 | |
473 | [inline] |
474 | pub fn geteuid() int { |
475 | return C.geteuid() |
476 | } |
477 | |
478 | [inline] |
479 | pub fn getgid() int { |
480 | return C.getgid() |
481 | } |
482 | |
483 | [inline] |
484 | pub fn getegid() int { |
485 | return C.getegid() |
486 | } |
487 | |
488 | // Turns the given bit on or off, depending on the `enable` parameter |
489 | pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) { |
490 | mut s := C.stat{} |
491 | mut new_mode := u32(0) |
492 | path := &char(path_s.str) |
493 | unsafe { |
494 | C.stat(path, &s) |
495 | new_mode = s.st_mode |
496 | } |
497 | match enable { |
498 | true { new_mode |= mode } |
499 | false { new_mode &= (0o7777 - mode) } |
500 | } |
501 | C.chmod(path, int(new_mode)) |
502 | } |
503 | |
504 | // get_long_path has no meaning for *nix, but has for windows, where `c:\folder\some~1` for example |
505 | // can be the equivalent of `c:\folder\some spa ces`. On *nix, it just returns a copy of the input path. |
506 | fn get_long_path(path string) !string { |
507 | return path |
508 | } |