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