v4 / vlib / os / os_nix.c.v
621 lines · 568 sloc · 16.3 KB · 288feee4702b35beadb4037d02b96b5c8674156b
Raw
1module os
2
3#include <dirent.h>
4#include <unistd.h>
5#include <fcntl.h>
6#include <sys/utsname.h>
7#include <sys/types.h>
8#include <sys/statvfs.h>
9#include <sys/wait.h>
10#include <utime.h>
11#insert "@VEXEROOT/vlib/os/execute_capture_nix.h"
12
13// short_path is a Windows-only helper that returns the DOS 8.3 short path.
14// On non-Windows platforms it simply returns the given path unchanged,
15// so that callers guarded by `$if windows { ... }` type-check on other targets.
16pub fn short_path(path string) string {
17 return path
18}
19
20// path_separator is the platform specific separator string, used between the folders and filenames in a path. It is '/' on POSIX, and '\\' on Windows.
21pub const path_separator = '/'
22
23// path_delimiter is the platform specific delimiter string, used between the paths in environment variables like PATH. It is ':' on POSIX, and ';' on Windows.
24pub const path_delimiter = ':'
25
26// path_devnull is a platform-specific file path of the null device.
27// It is '/dev/null' on POSIX, and r'\\.\nul' on Windows.
28pub const path_devnull = '/dev/null'
29
30const executable_suffixes = ['']
31
32const stdin_value = 0
33const stdout_value = 1
34const stderr_value = 2
35
36// (Must be realized in Syscall) (Must be specified)
37// ref: http://www.ccfit.nsu.ru/~deviv/courses/unix/unix/ng7c229.html
38pub const s_ifmt = 0xF000 // type of file
39pub const s_ifdir = 0x4000 // directory
40pub const s_ifreg = 0x8000 // regular file
41pub const s_iflnk = 0xa000 // link
42pub const s_isuid = 0o4000 // SUID
43pub const s_isgid = 0o2000 // SGID
44pub const s_isvtx = 0o1000 // Sticky
45pub const s_irusr = 0o0400 // Read by owner
46pub const s_iwusr = 0o0200 // Write by owner
47pub const s_ixusr = 0o0100 // Execute by owner
48pub const s_irgrp = 0o0040 // Read by group
49pub const s_iwgrp = 0o0020 // Write by group
50pub const s_ixgrp = 0o0010 // Execute by group
51pub const s_iroth = 0o0004 // Read by others
52pub const s_iwoth = 0o0002 // Write by others
53pub const s_ixoth = 0o0001
54
55fn C.utime(&char, &C.utimbuf) i32
56
57fn C.uname(name &C.utsname) i32
58
59fn C.symlink(&char, &char) i32
60
61fn C.readlink(&char, &char, i32) i32
62
63fn C.link(&char, &char) i32
64
65fn C.gethostname(&char, i32) i32
66
67// Note: not available on Android fn C.getlogin_r(&char, int) int
68fn C.getlogin() &char
69
70fn C.getppid() i32
71
72fn C.getgid() i32
73
74fn C.getegid() i32
75
76fn C.v_os_execute_capture_start(cmd &char, child_pid &int, read_fd &int) int
77
78fn C.v_os_exec_capture_start(argv &&char, child_pid &int, read_fd &int) int
79
80enum GlobMatch {
81 exact
82 ends_with
83 starts_with
84 start_and_ends_with
85 contains
86 any
87}
88
89fn glob_match(dir string, pattern string, next_pattern string, mut matches []string) []string {
90 mut subdirs := []string{}
91 if is_file(dir) {
92 return subdirs
93 }
94 mut files := ls(dir) or { return subdirs }
95 mut mode := GlobMatch.exact
96 mut pat := pattern
97 if pat == '*' {
98 mode = GlobMatch.any
99 if next_pattern != pattern && next_pattern != '' {
100 for file in files {
101 if is_dir('${dir}/${file}') {
102 subdirs << '${dir}/${file}'
103 }
104 }
105 return subdirs
106 }
107 }
108 if pat == '**' {
109 files = walk_ext(dir, '')
110 pat = next_pattern
111 }
112 if pat.starts_with('*') {
113 mode = .ends_with
114 pat = pat[1..]
115 }
116 if pat.ends_with('*') {
117 mode = if mode == .ends_with { GlobMatch.contains } else { GlobMatch.starts_with }
118 pat = pat[..pat.len - 1]
119 }
120 if pat.contains('*') {
121 mode = .start_and_ends_with
122 }
123 for file in files {
124 mut fpath := file
125 f := if file.contains(path_separator) {
126 pathwalk := file.split(path_separator)
127 pathwalk[pathwalk.len - 1]
128 } else {
129 fpath = if dir == '.' { file } else { '${dir}/${file}' }
130 file
131 }
132 if f in ['.', '..'] || f == '' {
133 continue
134 }
135 hit := match mode {
136 .any {
137 true
138 }
139 .exact {
140 f == pat
141 }
142 .starts_with {
143 f.starts_with(pat)
144 }
145 .ends_with {
146 f.ends_with(pat)
147 }
148 .start_and_ends_with {
149 p := pat.split('*')
150 f.starts_with(p[0]) && f.ends_with(p[1])
151 }
152 .contains {
153 f.contains(pat)
154 }
155 }
156
157 if hit {
158 if is_dir(fpath) {
159 subdirs << fpath
160 if next_pattern == pattern && next_pattern != '' {
161 matches << '${fpath}${path_separator}'
162 }
163 } else {
164 matches << fpath
165 }
166 }
167 }
168 return subdirs
169}
170
171fn native_glob_pattern(pattern string, mut matches []string) ! {
172 steps := pattern.split(path_separator)
173 cwd := if pattern.starts_with(path_separator) { path_separator } else { '.' }
174 mut subdirs := [cwd]
175 for i := 0; i < steps.len; i++ {
176 step := steps[i]
177 step2 := if i + 1 == steps.len { step } else { steps[i + 1] }
178 if step == '' {
179 continue
180 }
181 if is_dir('${cwd}${path_separator}${step}') {
182 dd := if cwd == '/' {
183 step
184 } else {
185 if cwd == '.' || cwd == '' {
186 step
187 } else {
188 if step == '.' || step == '/' { cwd } else { '${cwd}/${step}' }
189 }
190 }
191 if i + 1 != steps.len {
192 if dd !in subdirs {
193 subdirs << dd
194 }
195 }
196 }
197 mut subs := []string{}
198 for sd in subdirs {
199 d := if cwd == '/' {
200 sd
201 } else {
202 if cwd == '.' || cwd == '' {
203 sd
204 } else {
205 if sd == '.' || sd == '/' { cwd } else { '${cwd}/${sd}' }
206 }
207 }
208 subs << glob_match(d.replace('//', '/'), step, step2, mut matches)
209 }
210 subdirs = subs.clone()
211 }
212}
213
214// utime changes the access and modification times of the inode specified by path.
215// It returns POSIX error message, if it can not do so.
216pub fn utime(path string, actime i64, modtime i64) ! {
217 u := C.utimbuf{actime, modtime}
218 if C.utime(&char(path.str), &u) != 0 {
219 return error_with_code(posix_get_error_msg(C.errno), C.errno)
220 }
221}
222
223// uname returns information about the platform on which the program is running.
224// For example:
225// os.Uname{
226// sysname: 'Linux'
227// nodename: 'nemesis'
228// release: '5.15.0-57-generic'
229// version: '#63~20.04.1-Ubuntu SMP Wed Nov 30 13:40:16 UTC 2022'
230// machine: 'x86_64'
231// }
232// where the fields have the following meaning:
233// sysname is the name of this implementation of the operating system
234// nodename is the name of this node within an implementation-dependent communications network
235// release is the current release level of this implementation
236// version is the current version level of this release
237// machine is the name of the hardware type, on which the system is running
238// See also https://pubs.opengroup.org/onlinepubs/7908799/xsh/sysutsname.h.html
239pub fn uname() Uname {
240 mut u := Uname{}
241 unsafe {
242 d := &C.utsname(malloc_noscan(int(sizeof(C.utsname))))
243 if C.uname(d) == 0 {
244 u.sysname = cstring_to_vstring(d.sysname)
245 u.nodename = cstring_to_vstring(d.nodename)
246 u.release = cstring_to_vstring(d.release)
247 u.version = cstring_to_vstring(d.version)
248 u.machine = cstring_to_vstring(d.machine)
249 }
250 free(d)
251 }
252 return u
253}
254
255// hostname returns the hostname (system's DNS name) or POSIX error message if the hostname call fails.
256pub fn hostname() !string {
257 mut hstnme := ''
258 size := 256
259 buf := unsafe { &char(malloc_noscan(size)) }
260 if C.gethostname(buf, size) == 0 {
261 hstnme = unsafe { cstring_to_vstring(buf) }
262 unsafe { free(buf) }
263 return hstnme
264 }
265 return error(posix_get_error_msg(C.errno))
266}
267
268// loginname returns the name of the user logged in on the controlling terminal of the process.
269// It returns a POSIX error message, if the getlogin call fails.
270pub fn loginname() !string {
271 x := C.getlogin()
272 if !isnil(x) {
273 return unsafe { cstring_to_vstring(x) }
274 }
275 return error(posix_get_error_msg(C.errno))
276}
277
278// ls returns ![]string of the files and dirs in the given `path` ( os.ls uses C.readdir ). Symbolic links are returned to be files. For recursive list see os.walk functions.
279// See also: `os.walk`, `os.walk_ext`, `os.is_dir`, `os.is_file`
280// Example:
281// ```
282// entries := os.ls(os.home_dir()) or { [] }
283// for entry in entries {
284// if os.is_dir(os.join_path(os.home_dir(), entry)) {
285// println('dir: ${entry}')
286// } else {
287// println('file: ${entry}')
288// }
289// }
290// ```
291@[manualfree]
292pub fn ls(path string) ![]string {
293 if path == '' {
294 return error('ls() expects a folder, not an empty string')
295 }
296 mut res := []string{cap: 50}
297 dir_ptr := unsafe { C.opendir(&char(path.str)) }
298 if isnil(dir_ptr) {
299 return error_posix(msg: 'ls() couldnt open dir "${path}"')
300 }
301 mut ent := &C.dirent(unsafe { nil })
302 for {
303 ent = C.readdir(dir_ptr)
304 if isnil(ent) {
305 break
306 }
307 unsafe {
308 bptr := &u8(&ent.d_name[0])
309 // vfmt off
310 if bptr[0] == 0 || (bptr[0] == `.` && bptr[1] == 0) || (bptr[0] == `.` && bptr[1] == `.` && bptr[2] == 0) {
311 continue
312 }
313 res << tos_clone(bptr)
314 // vfmt on
315 }
316 }
317 C.closedir(dir_ptr)
318 return res
319}
320
321// mkdir creates a new directory with the specified path.
322pub fn mkdir(path string, params MkdirParams) ! {
323 if path == '.' {
324 return
325 }
326 apath := real_path(path)
327 r := unsafe { C.mkdir(&char(apath.str), params.mode) }
328 if r == -1 {
329 return error(posix_get_error_msg(C.errno))
330 }
331}
332
333// execute starts the specified command, waits for it to complete, and returns its output.
334pub fn execute(cmd string) Result {
335 mut pid := 0
336 mut read_fd := -1
337 v_os_execute_lock()
338 rc := C.v_os_execute_capture_start(&char(cmd.str), &pid, &read_fd)
339 v_os_execute_unlock()
340 if rc != 0 {
341 return Result{
342 exit_code: -1
343 output: 'exec("${cmd}") failed'
344 }
345 }
346 soutput := fd_slurp(read_fd).join('')
347 fd_close(read_fd)
348 mut status := 0
349 for {
350 C.errno = 0
351 if C.waitpid(pid, &status, 0) != -1 {
352 break
353 }
354 if C.errno == C.EINTR {
355 continue
356 }
357 return Result{
358 exit_code: -1
359 output: soutput
360 }
361 }
362 exit_code, _ := posix_wait4_to_exit_status(status)
363 return Result{
364 exit_code: exit_code
365 output: soutput
366 }
367}
368
369// exec starts the specified command with arguments, waits for it to complete, and returns its output.
370pub fn exec(args []string) Result {
371 if args.len == 0 {
372 return Result{
373 exit_code: -1
374 output: 'exec requires at least one argument'
375 }
376 }
377 mut cargs := []&char{cap: args.len + 1}
378 for arg in args {
379 cargs << &char(arg.str)
380 }
381 cargs << &char(unsafe { nil })
382 mut pid := 0
383 mut read_fd := -1
384 v_os_execute_lock()
385 rc := C.v_os_exec_capture_start(cargs.data, &pid, &read_fd)
386 v_os_execute_unlock()
387 if rc != 0 {
388 return Result{
389 exit_code: -1
390 output: 'exec("${args[0]}") failed'
391 }
392 }
393 soutput := fd_slurp(read_fd).join('')
394 fd_close(read_fd)
395 mut status := 0
396 for {
397 C.errno = 0
398 if C.waitpid(pid, &status, 0) != -1 {
399 break
400 }
401 if C.errno == C.EINTR {
402 continue
403 }
404 return Result{
405 exit_code: -1
406 output: soutput
407 }
408 }
409 exit_code, _ := posix_wait4_to_exit_status(status)
410 return Result{
411 exit_code: exit_code
412 output: soutput
413 }
414}
415
416// raw_execute does the same as `execute` on Unix platforms.
417// On Windows raw_execute starts the specified command, waits for it to complete, and returns its output.
418// It's marked as `unsafe` to help emphasize the problems that may arise by allowing, for example,
419// user provided escape sequences.
420@[unsafe]
421pub fn raw_execute(cmd string) Result {
422 return execute(cmd)
423}
424
425// symlink creates a symbolic link named link_name, which points to target.
426// It returns a POSIX error message, if it can not do so.
427pub fn symlink(target string, link_name string) ! {
428 res := C.symlink(&char(target.str), &char(link_name.str))
429 if res == 0 {
430 return
431 }
432 return error(posix_get_error_msg(C.errno))
433}
434
435// readlink reads the target of a symbolic link.
436// It returns a POSIX error message if it can not do so.
437//
438// Note that the target of a symbolic link can be any string:
439// it is often used to point to another path, but the target is not guaranteed
440// to resolve as a path, nor to point to a path that exists.
441@[manualfree]
442pub fn readlink(path string) !string {
443 // Use a region of stack to get information into; we'll return new memory of more precise size later.
444 mut buf := [max_path_buffer_size]u8{}
445 // readlink returns the number of bytes written into buf, or -1 for errors.
446 res := C.readlink(&char(path.str), &char(&buf[0]), max_path_buffer_size)
447 if res < 0 {
448 return last_error()
449 }
450 // Common case: we got a complete read into our buffer on the stack.
451 // In this case, copy the data into a new heap-allocated string that's right-sized
452 // (we can't return memory from our stack).
453 if res < max_path_buffer_size {
454 return unsafe { (&buf[0]).vstring_with_len(res).clone() }
455 }
456 // If the number of bytes read wasn't less than as many as we said we'd accept: that means we might not have gotten a complete read.
457 // In this case, we have to start doing heap allocations, increasingly large, and simply check until we get a complete one.
458 // Whenever we do succeed: we'll return a string that refers to a subset of that possibly excessively sized buffer,
459 // because we're already on the heap and returning it is valid; and because allocating a new buffer just
460 // to save some resident memory is usually a poor trade of spending of time just to reclaim a very minor amount of space.
461 mut size := max_path_buffer_size
462 for {
463 size *= 2
464 mut buf2 := unsafe { &char(malloc_noscan(size)) }
465 res2 := C.readlink(&char(path.str), buf2, size)
466 if res2 < 0 {
467 return last_error()
468 }
469 if res2 < size {
470 unsafe {
471 buf2[res2] = 0
472 return cstring_to_vstring(buf2)
473 }
474 }
475 unsafe { free(buf2) } // and then loop around to try again with a larger one.
476 }
477 return error('${@METHOD} unreachable code')
478}
479
480// link creates a new link (also known as a hard link) to an existing file.
481// It returns a POSIX error message, if it can not do so.
482pub fn link(origin string, target string) ! {
483 res := C.link(&char(origin.str), &char(target.str))
484 if res == 0 {
485 return
486 }
487 return error(posix_get_error_msg(C.errno))
488}
489
490// get_error_msg return error code representation in string.
491pub fn get_error_msg(code int) string {
492 return posix_get_error_msg(code)
493}
494
495pub fn (mut f File) close() {
496 if !f.is_opened {
497 return
498 }
499 f.is_opened = false
500 cfile := f.cfile
501 f.cfile = unsafe { nil }
502 C.fflush(cfile)
503 C.fclose(cfile)
504}
505
506fn C.mkstemp(stemplate &u8) i32
507
508// ensure_folder_is_writable checks that `folder` exists, and is writable to the process
509// by creating an empty file in it, then deleting it.
510@[manualfree]
511pub fn ensure_folder_is_writable(folder string) ! {
512 if !exists(folder) {
513 return error_with_code('`${folder}` does not exist', 1)
514 }
515 if !is_dir(folder) {
516 return error_with_code('`${folder}` is not a folder', 2)
517 }
518 tmp_perm_check := join_path_single(folder, 'XXXXXX')
519 defer {
520 unsafe { tmp_perm_check.free() }
521 }
522 unsafe {
523 x := C.mkstemp(&char(tmp_perm_check.str))
524 if -1 == x {
525 return error_with_code('folder `${folder}` is not writable', 3)
526 }
527 C.close(x)
528 }
529 rm(tmp_perm_check)!
530}
531
532// getpid returns the process ID (PID) of the calling process.
533@[inline]
534pub fn getpid() int {
535 return C.getpid()
536}
537
538// getppid returns the process ID of the parent of the calling process.
539@[inline]
540pub fn getppid() int {
541 return C.getppid()
542}
543
544// getuid returns the real user ID of the calling process.
545@[inline]
546pub fn getuid() int {
547 return C.getuid()
548}
549
550// geteuid returns the effective user ID of the calling process.
551@[inline]
552pub fn geteuid() int {
553 return C.geteuid()
554}
555
556// getgid returns the real group ID of the calling process.
557@[inline]
558pub fn getgid() int {
559 return C.getgid()
560}
561
562// getegid returns the effective group ID of the calling process.
563@[inline]
564pub fn getegid() int {
565 return C.getegid()
566}
567
568// Turns the given bit on or off, depending on the `enable` parameter.
569pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) {
570 mut new_mode := u32(0)
571 if s := stat(path_s) {
572 new_mode = s.mode
573 }
574 match enable {
575 true { new_mode |= mode }
576 false { new_mode &= (0o7777 - mode) }
577 }
578
579 C.chmod(&char(path_s.str), int(new_mode))
580}
581
582// get_long_path has no meaning for *nix, but has for windows, where `c:\folder\some~1` for example
583// can be the equivalent of `c:\folder\some spa ces`. On *nix, it just returns a copy of the input path.
584fn get_long_path(path string) !string {
585 return path
586}
587
588fn C.sysconf(name i32) i64
589
590// page_size returns the page size in bytes.
591pub fn page_size() int {
592 return int(C.sysconf(C._SC_PAGESIZE))
593}
594
595struct C.statvfs {
596 f_bsize usize
597 f_blocks usize
598 f_bfree usize
599 f_bavail usize
600}
601
602// disk_usage returns disk usage of `path`.
603@[manualfree]
604pub fn disk_usage(path string) !DiskUsage {
605 mpath := if path == '' { '.' } else { path }
606 defer { unsafe { mpath.free() } }
607 mut vfs := C.statvfs{}
608 ret := unsafe { C.statvfs(&char(mpath.str), &vfs) }
609 if ret == -1 {
610 return error('cannot get disk usage of path')
611 }
612 f_bsize := u64(vfs.f_bsize)
613 f_blocks := u64(vfs.f_blocks)
614 f_bavail := u64(vfs.f_bavail)
615 f_bfree := u64(vfs.f_bfree)
616 return DiskUsage{
617 total: f_bsize * f_blocks
618 available: f_bsize * f_bavail
619 used: f_bsize * (f_blocks - f_bfree)
620 }
621}
622