v / vlib / os
Raw file | 508 loc (464 sloc) | 11.08 KB | Latest commit hash f69b994c7
1module os
2
3import 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
12pub const (
13 path_separator = '/'
14 path_delimiter = ':'
15)
16
17const executable_suffixes = ['']
18
19const (
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
27pub 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
45fn C.utime(&char, voidptr) int
46
47fn C.uname(name voidptr) int
48
49fn C.symlink(&char, &char) int
50
51fn C.link(&char, &char) int
52
53fn C.gethostname(&char, int) int
54
55// Note: not available on Android fn C.getlogin_r(&char, int) int
56fn C.getlogin() &char
57
58fn C.getppid() int
59
60fn C.getgid() int
61
62fn C.getegid() int
63
64enum GlobMatch {
65 exact
66 ends_with
67 starts_with
68 start_and_ends_with
69 contains
70 any
71}
72
73fn 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
154fn 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
197pub 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
220pub 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
238pub 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
250pub 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
258fn 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
266pub 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.
296pub 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]
309pub 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]
353pub fn raw_execute(cmd string) Result {
354 return execute(cmd)
355}
356
357[manualfree]
358pub 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]
370pub 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
395pub 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
402pub 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
410pub 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.
419pub fn get_error_msg(code int) string {
420 return posix_get_error_msg(code)
421}
422
423pub 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
432fn 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]
437pub 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]
459pub fn getpid() int {
460 return C.getpid()
461}
462
463[inline]
464pub fn getppid() int {
465 return C.getppid()
466}
467
468[inline]
469pub fn getuid() int {
470 return C.getuid()
471}
472
473[inline]
474pub fn geteuid() int {
475 return C.geteuid()
476}
477
478[inline]
479pub fn getgid() int {
480 return C.getgid()
481}
482
483[inline]
484pub fn getegid() int {
485 return C.getegid()
486}
487
488// Turns the given bit on or off, depending on the `enable` parameter
489pub 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.
506fn get_long_path(path string) !string {
507 return path
508}