diff --git a/vlib/os/os_c.v b/vlib/os/os_c.v index cce104ada5..ef142c57fd 100644 --- a/vlib/os/os_c.v +++ b/vlib/os/os_c.v @@ -728,7 +728,9 @@ pub fn is_dir(path string) bool { // is_link returns a boolean indicating whether `path` is a link. pub fn is_link(path string) bool { $if windows { - return false // TODO + path_ := path.replace('/', '\\') + attr := C.GetFileAttributesW(path_.to_wide()) + return int(attr) != int(C.INVALID_FILE_ATTRIBUTES) && (attr & 0x400) != 0 } $else { statbuf := C.stat{} if C.lstat(&char(path.str), &statbuf) != 0 { @@ -784,26 +786,45 @@ pub fn real_path(fpath string) string { defer { unsafe { free(fullpath) } } - + mut res := '' $if windows { + /* + // GetFullPathName doesn't work with symbolic links + // TODO: TCC32 gets runtime error + max := 512 + size := max * 2 // max_path_len * sizeof(wchar_t) + // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 + // use get_file_handle instead of C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) + // try to open the file to get symbolic link path + file := get_file_handle(fpath) + if file != voidptr(-1) { + fullpath = unsafe { &u16(vcalloc(size)) } + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew + final_len := C.GetFinalPathNameByHandleW(file, fullpath, size, 0) + if final_len < size { + ret := unsafe { string_from_wide2(fullpath, final_len) } + res = ret[4..] + } else { + eprintln('os.real_path() saw that the file path was too long') + } + } else { + */ + // if it is not a file get full path fullpath = unsafe { &u16(vcalloc(max_path_len * 2)) } // TODO: check errors if path len is not enough ret := C.GetFullPathName(fpath.to_wide(), max_path_len, fullpath, 0) if ret == 0 { return fpath } + res = unsafe { string_from_wide(fullpath) } + //} + // C.CloseHandle(file) } $else { fullpath = vcalloc(max_path_len) ret := &char(C.realpath(&char(fpath.str), &char(fullpath))) if ret == 0 { return fpath } - } - - mut res := '' - $if windows { - res = unsafe { string_from_wide(fullpath) } - } $else { res = unsafe { fullpath.vstring() } } nres := normalize_drive_letter(res) diff --git a/vlib/os/os_nix.c.v b/vlib/os/os_nix.c.v index 777a8e2027..ab5a855b7f 100644 --- a/vlib/os/os_nix.c.v +++ b/vlib/os/os_nix.c.v @@ -51,11 +51,19 @@ fn C.uname(name voidptr) int fn C.symlink(&char, &char) int +fn C.link(&char, &char) int + fn C.gethostname(&char, int) int // NB: not available on Android fn C.getlogin_r(&char, int) int fn C.getlogin() &char +fn C.getppid() int + +fn C.getgid() int + +fn C.getegid() int + pub fn uname() Uname { mut u := Uname{} utsize := sizeof(C.utsname) @@ -279,6 +287,14 @@ pub fn symlink(origin string, target string) ?bool { return error(posix_get_error_msg(C.errno)) } +pub fn link(origin string, target string) ?bool { + res := C.link(&char(origin.str), &char(target.str)) + if res == 0 { + return true + } + return error(posix_get_error_msg(C.errno)) +} + // get_error_msg return error code representation in string. pub fn get_error_msg(code int) string { return posix_get_error_msg(code) @@ -332,6 +348,11 @@ pub fn getpid() int { return C.getpid() } +[inline] +pub fn getppid() int { + return C.getppid() +} + [inline] pub fn getuid() int { return C.getuid() @@ -342,6 +363,16 @@ pub fn geteuid() int { return C.geteuid() } +[inline] +pub fn getgid() int { + return C.getgid() +} + +[inline] +pub fn getegid() int { + return C.getegid() +} + // Turns the given bit on or off, depending on the `enable` parameter pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) { mut s := C.stat{} diff --git a/vlib/os/os_test.v b/vlib/os/os_test.v index a9c3813574..22a767dc97 100644 --- a/vlib/os/os_test.v +++ b/vlib/os/os_test.v @@ -311,28 +311,62 @@ fn test_is_writable_folder() { } fn test_make_symlink_check_is_link_and_remove_symlink() { - $if windows { - // TODO - assert true - return - } folder := 'tfolder' symlink := 'tsymlink' - os.rm(symlink) or {} - os.rm(folder) or {} + // windows creates a directory symlink, so delete it with rmdir() + $if windows { + os.rmdir(symlink) or {} + } $else { + os.rm(symlink) or {} + } + os.rmdir(folder) or {} os.mkdir(folder) or { panic(err) } folder_contents := os.ls(folder) or { panic(err) } assert folder_contents.len == 0 - os.system('ln -s $folder $symlink') + os.symlink(folder, symlink) or { panic(err) } assert os.is_link(symlink) - os.rm(symlink) or { panic(err) } - os.rm(folder) or { panic(err) } + $if windows { + os.rmdir(symlink) or { panic(err) } + } $else { + os.rm(symlink) or { panic(err) } + } + os.rmdir(folder) or { panic(err) } folder_exists := os.is_dir(folder) assert folder_exists == false symlink_exists := os.is_link(symlink) assert symlink_exists == false } +fn test_make_symlink_check_is_link_and_remove_symlink_with_file() { + file := 'tfile' + symlink := 'tsymlink' + os.rm(symlink) or {} + os.rm(file) or {} + mut f := os.create(file) or { panic(err) } + f.close() + os.symlink(file, symlink) or { panic(err) } + assert os.is_link(symlink) + os.rm(symlink) or { panic(err) } + os.rm(file) or { panic(err) } + symlink_exists := os.is_link(symlink) + assert symlink_exists == false +} + +fn test_make_hardlink_check_is_link_and_remove_hardlink_with_file() { + file := 'tfile' + symlink := 'tsymlink' + os.rm(symlink) or {} + os.rm(file) or {} + mut f := os.create(file) or { panic(err) } + f.close() + os.link(file, symlink) or { panic(err) } + assert os.exists(symlink) + os.rm(symlink) or { panic(err) } + os.rm(file) or { panic(err) } + symlink_exists := os.is_link(symlink) + assert symlink_exists == false +} + // fn test_fork() { // pid := os.fork() // if pid == 0 { @@ -355,15 +389,16 @@ fn test_make_symlink_check_is_link_and_remove_symlink() { // } // } fn test_symlink() { - $if windows { - return - } os.mkdir('symlink') or { panic(err) } os.symlink('symlink', 'symlink2') or { panic(err) } assert os.exists('symlink2') // cleanup - os.rm('symlink') or { panic(err) } - os.rm('symlink2') or { panic(err) } + os.rmdir('symlink') or { panic(err) } + $if windows { + os.rmdir('symlink2') or { panic(err) } + } $else { + os.rm('symlink2') or { panic(err) } + } } fn test_is_executable_writable_readable() { diff --git a/vlib/os/os_windows.c.v b/vlib/os/os_windows.c.v index 9cfb26e8bc..9e22976491 100644 --- a/vlib/os/os_windows.c.v +++ b/vlib/os/os_windows.c.v @@ -5,6 +5,15 @@ import strings #flag windows -l advapi32 #include +// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw +fn C.CreateSymbolicLinkW(&u16, &u16, u32) int + +// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw +// TCC gets builder error +// fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) int + +fn C._getpid() int + pub const ( path_separator = '\\' path_delimiter = ';' @@ -138,7 +147,7 @@ pub fn mkdir(path string) ?bool { } apath := real_path(path) if !C.CreateDirectory(apath.to_wide(), 0) { - return error('mkdir failed for "$apath", because CreateDirectory returned ' + + return error('mkdir failed for "$apath", because CreateDirectory returned: ' + get_error_msg(int(C.GetLastError()))) } return true @@ -310,21 +319,46 @@ pub fn execute(cmd string) Result { } } -// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw -fn C.CreateSymbolicLinkW(&u16, &u16, u32) int +pub fn symlink(origin string, target string) ?bool { + // this is a temporary fix for TCC32 due to runtime error + // TODO: patch TCC32 + $if x64 || x32 { + mut flags := 0 + if is_dir(origin) { + flags ^= 1 + } -pub fn symlink(symlink_path string, target_path string) ?bool { - mut flags := 0 - if is_dir(symlink_path) { - flags |= 1 + flags ^= 2 // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE + res := C.CreateSymbolicLinkW(target.to_wide(), origin.to_wide(), flags) + + // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10 + if res != 1 { + return error(get_error_msg(int(C.GetLastError()))) + } + if !exists(target) { + return error('C.CreateSymbolicLinkW reported success, but symlink still does not exist') + } + return true } - flags |= 2 // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE - res := C.CreateSymbolicLinkW(symlink_path.to_wide(), target_path.to_wide(), flags) - if res == 0 { + return false +} + +pub fn link(origin string, target string) ?bool { + /* + // TODO: TCC gets builder error + res := C.CreateHardLinkW(target.to_wide(), origin.to_wide(), C.NULL) + // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10 + if res != 1 { return error(get_error_msg(int(C.GetLastError()))) } - if !exists(symlink_path) { - return error('C.CreateSymbolicLinkW reported success, but symlink still does not exist') + if !exists(target) { + return error('C.CreateHardLinkW reported success, but link still does not exist') + } + return true + */ + res := execute('fsutil hardlink create $target $origin') + if res.exit_code != 0 { + return error(res.output) } return true } @@ -426,13 +460,36 @@ pub fn is_writable_folder(folder string) ?bool { return true } -fn C._getpid() int - [inline] pub fn getpid() int { return C._getpid() } +[inline] +pub fn getppid() int { + return 0 +} + +[inline] +pub fn getuid() int { + return 0 +} + +[inline] +pub fn geteuid() int { + return 0 +} + +[inline] +pub fn getgid() int { + return 0 +} + +[inline] +pub fn getegid() int { + return 0 +} + pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) { // windows has no concept of a permission mask, so do nothing }