diff --git a/doc/specs/stdlib_system.md b/doc/specs/stdlib_system.md index 96eebb2e8..3806dfc91 100644 --- a/doc/specs/stdlib_system.md +++ b/doc/specs/stdlib_system.md @@ -456,6 +456,156 @@ The function returns a `logical` value: --- +## `make_directory` - Creates an empty directory + +### Status + +Experimental + +### Description + +It creates an empty directory. +It is designed to work across multiple platforms. On Windows, paths with both forward `/` and backward `\` slashes are accepted. + +### Syntax + +`call [[stdlib_system(module):make_directory(subroutine)]] (path, mode, err)` + +### Class + +Subroutine + +### Arguments + +`path`: Shall be a character string containing the path of the directory to create. It is an `intent(in)` argument. + +`mode`: Shall be a scalar integer indicating the permission bits required (Not applicable to Windows). It is an `optional, intent(in)` argument. + +`err`: Shall be of type `state_type`, for error handling. It is an `optional, intent(out)` argument. + +### Return values + +The `err` is set accordingly. + +### Example + +```fortran +{!example/system/example_make_directory.f90!} +``` + +--- + +## `remove_directory` - Removes an empty directory + +### Status + +Experimental + +### Description + +It deletes an empty directory. +It is designed to work across multiple platforms. On Windows, paths with both forward `/` and backward `\` slashes are accepted. + +### Syntax + +`call [[stdlib_system(module):remove_directory(subroutine)]] (path, err)` + +### Class + +Subroutine + +### Arguments + +`path`: Shall be a character string containing the path of the directory to create. It is an `intent(in)` argument. + +`err`: Shall be of type `state_type`, for error handling. It is an `intent(out)` argument. + +### Return values + +The `err` is set accordingly. + +### Example + +```fortran +{!example/system/example_remove_directory.f90!} +``` + +--- + +## `get_cwd` - Gets the current working directory + +### Status + +Experimental + +### Description + +It gets the current working directory associated with the process calling this subroutine. +It is designed to work across multiple platforms. On Windows, paths with both forward `/` and backward `\` slashes are accepted. + +### Syntax + +`call [[stdlib_system(module):get_cwd(subroutine)]] (cwd, err)` + +### Class + +Subroutine + +### Arguments + +`cwd`: Shall be a character string containing the path of the current working directory (cwd). It is an `intent(out)` argument. + +`err`: Shall be of type `state_type`, for error handling. It is an `intent(out)` argument. + +### Return values + +The `err` is set accordingly. + +### Example + +```fortran +{!example/system/example_cwd.f90!} +``` + +--- + +## `set_cwd` - Sets the current working directory + +### Status + +Experimental + +### Description + +It sets the current working directory associated with the process calling this subroutine. +It is designed to work across multiple platforms. On Windows, paths with both forward `/` and backward `\` slashes are accepted. + +### Syntax + +`call [[stdlib_system(module):set_cwd(subroutine)]] (path, err)` + +### Class + +Subroutine + +### Arguments + +`path`: Shall be a character string containing the path of the directory. It is an `intent(in)` argument. + +`err`: Shall be of type `state_type`, for error handling. It is an `intent(out)` argument. + +### Return values + +The `err` is set accordingly. + +### Example + +```fortran +{!example/system/example_cwd.f90!} +``` + +--- + ## `null_device` - Return the null device file path ### Status diff --git a/example/system/CMakeLists.txt b/example/system/CMakeLists.txt index a2a7525c9..0784c9ad1 100644 --- a/example/system/CMakeLists.txt +++ b/example/system/CMakeLists.txt @@ -11,3 +11,6 @@ ADD_EXAMPLE(process_5) ADD_EXAMPLE(process_6) ADD_EXAMPLE(process_7) ADD_EXAMPLE(sleep) +ADD_EXAMPLE(make_directory) +ADD_EXAMPLE(remove_directory) +ADD_EXAMPLE(cwd) diff --git a/example/system/example_cwd.f90 b/example/system/example_cwd.f90 new file mode 100644 index 000000000..367a6f97e --- /dev/null +++ b/example/system/example_cwd.f90 @@ -0,0 +1,33 @@ +! Illustrate the usage of get_cwd, set_cwd +program example_cwd + use stdlib_system, only: get_cwd, set_cwd + use stdlib_error, only: state_type + implicit none + + character(len=:), allocatable :: path + type(state_type) :: err + + call get_cwd(path, err) + + if (err%error()) then + print *, "Error getting current working directory: "//err%print() + end if + + print *, "CWD: "//path + + call set_cwd("./src", err) + + if (err%error()) then + print *, "Error setting current working directory: "//err%print() + end if + + call get_cwd(path, err) + + if (err%error()) then + print *, "Error getting current working directory after using set_cwd: "//err%print() + return + end if + + print *, "CWD: "//path +end program example_cwd + diff --git a/example/system/example_make_directory.f90 b/example/system/example_make_directory.f90 new file mode 100644 index 000000000..5e551b810 --- /dev/null +++ b/example/system/example_make_directory.f90 @@ -0,0 +1,17 @@ +! Illustrate the usage of make_directory +program example_make_directory + use stdlib_system, only: make_directory, is_directory + use stdlib_error, only: state_type + implicit none + + type(state_type) :: err + + call make_directory("test", err=err) + + if (err%error()) then + print *, err%print() + else + print *, "directory created sucessfully" + end if + +end program example_make_directory diff --git a/example/system/example_remove_directory.f90 b/example/system/example_remove_directory.f90 new file mode 100644 index 000000000..993adf4f9 --- /dev/null +++ b/example/system/example_remove_directory.f90 @@ -0,0 +1,17 @@ +! Illustrate the usage of remove_directory +program example_remove_directory + use stdlib_system, only: make_directory, is_directory, remove_directory + use stdlib_error, only: state_type + implicit none + + type(state_type) :: err + + call remove_directory("directory_to_be_removed", err) + + if (err%error()) then + print *, err%print() + else + print *, "directory removed successfully" + end if + +end program example_remove_directory diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3cd99120..24fd9c56b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,14 +32,14 @@ set(fppFiles stdlib_linalg_kronecker.fypp stdlib_linalg_cross_product.fypp stdlib_linalg_eigenvalues.fypp - stdlib_linalg_solve.fypp + stdlib_linalg_solve.fypp stdlib_linalg_determinant.fypp stdlib_linalg_qr.fypp stdlib_linalg_inverse.fypp stdlib_linalg_pinv.fypp stdlib_linalg_norms.fypp stdlib_linalg_state.fypp - stdlib_linalg_svd.fypp + stdlib_linalg_svd.fypp stdlib_linalg_cholesky.fypp stdlib_linalg_schur.fypp stdlib_optval.fypp @@ -116,6 +116,7 @@ set(SRC stdlib_sorting_radix_sort.f90 stdlib_system_subprocess.c stdlib_system_subprocess.F90 + stdlib_system.c stdlib_system.F90 stdlib_sparse.f90 stdlib_specialfunctions_legendre.f90 diff --git a/src/stdlib_system.F90 b/src/stdlib_system.F90 index a9c3e4d55..9d7557bb9 100644 --- a/src/stdlib_system.F90 +++ b/src/stdlib_system.F90 @@ -2,7 +2,7 @@ module stdlib_system use, intrinsic :: iso_c_binding, only : c_int, c_long, c_ptr, c_null_ptr, c_int64_t, c_size_t, & c_f_pointer use stdlib_kinds, only: int64, dp, c_bool, c_char -use stdlib_strings, only: to_c_char +use stdlib_strings, only: to_c_char, to_string use stdlib_error, only: state_type, STDLIB_SUCCESS, STDLIB_FS_ERROR implicit none private @@ -100,6 +100,62 @@ module stdlib_system !! public :: is_directory +!! version: experimental +!! +!! Makes an empty directory. +!! ([Specification](../page/specs/stdlib_system.html#make_directory)) +!! +!! ### Summary +!! Creates an empty directory with particular permissions. +!! +!! ### Description +!! This function makes an empty directory according to the path provided. +!! Relative paths as well as on Windows paths involving either `/` or `\` are accepted +!! appropriate error message is returned whenever any error occur. +!! +public :: make_directory + +!! version: experimental +!! +!! Removes an empty directory. +!! ([Specification](../page/specs/stdlib_system.html#remove_directory)) +!! +!! ### Summary +!! Deletes an empty directory. +!! +!! ### Description +!! This function deletes an empty directory according to the path provided. +!! Relative paths as well as on Windows paths involving either `/` or `\` are accepted. +!! appropriate error message is returned whenever any error occur. +!! +public :: remove_directory + +!! version: experimental +!! +!! Gets the current working directory of the process +!! ([Specification](../page/specs/stdlib_system.html#get_cwd)) +!! +!! ### Summary +!! Gets the current working directory. +!! +!! ### Description +!! This subroutine gets the current working directory of the process calling this function. +!! +public :: get_cwd + +!! version: experimental +!! +!! Sets the current working directory of the process +!! ([Specification](../page/specs/stdlib_system.html#set_cwd)) +!! +!! ### Summary +!! Changes the current working directory to the one specified. +!! +!! ### Description +!! This subroutine sets the current working directory of the process calling this function to the one specified. +!! +public :: set_cwd + !! version: experimental !! !! Deletes a specified file from the filesystem. @@ -690,6 +746,152 @@ end function stdlib_is_directory end function is_directory +function c_get_strerror() result(str) + character(len=:), allocatable :: str + + interface + type(c_ptr) function strerror(len) bind(C, name='stdlib_strerror') + import c_size_t, c_ptr + implicit none + integer(c_size_t), intent(out) :: len + end function strerror + end interface + + type(c_ptr) :: c_str_ptr + integer(c_size_t) :: len, i + character(kind=c_char), pointer :: c_str(:) + + c_str_ptr = strerror(len) + + call c_f_pointer(c_str_ptr, c_str, [len]) + + allocate(character(len=len) :: str) + + do concurrent (i=1:len) + str(i:i) = c_str(i) + end do +end function c_get_strerror + +!! makes an empty directory +subroutine make_directory(path, mode, err) + character(len=*), intent(in) :: path + integer, intent(in), optional :: mode + type(state_type), optional, intent(out) :: err + + integer :: code + type(state_type) :: err0 + + + interface + integer function stdlib_make_directory(cpath, cmode) bind(C, name='stdlib_make_directory') + import c_char + character(kind=c_char), intent(in) :: cpath(*) + integer, intent(in) :: cmode + end function stdlib_make_directory + end interface + + if (is_windows() .and. present(mode)) then + ! _mkdir() doesn't have a `mode` argument + err0 = state_type(STDLIB_FS_ERROR, "mode argument not present for Windows") + call err0%handle(err) + return + end if + + code = stdlib_make_directory(to_c_char(trim(path)), mode) + + select case (code) + case (0) + return + case default + ! error + err0 = state_type(STDLIB_FS_ERROR, "code:", to_string(code)//',', c_get_strerror()) + call err0%handle(err) + end select +end subroutine make_directory + +!! Removes an empty directory +subroutine remove_directory(path, err) + character(len=*), intent(in) :: path + type(state_type), optional, intent(out) :: err + + integer :: code + type(state_type) :: err0 + + interface + integer function stdlib_remove_directory(cpath) bind(C, name='stdlib_remove_directory') + import c_char + character(kind=c_char), intent(in) :: cpath(*) + end function stdlib_remove_directory + end interface + + code = stdlib_remove_directory(to_c_char(trim(path))) + + select case (code) + case (0) + return + case default + ! error + err0 = state_type(STDLIB_FS_ERROR, "code:", to_string(code)//',', c_get_strerror()) + call err0%handle(err) + end select +end subroutine remove_directory + +subroutine get_cwd(cwd, err) + character(:), allocatable, intent(out) :: cwd + type(state_type), intent(out) :: err + type(state_type) :: err0 + + interface + type(c_ptr) function stdlib_get_cwd(len, stat) bind(C, name='stdlib_get_cwd') + import c_ptr, c_size_t + integer(c_size_t), intent(out) :: len + integer :: stat + end function stdlib_get_cwd + end interface + + type(c_ptr) :: c_str_ptr + integer(c_size_t) :: len, i + integer :: stat + character(kind=c_char), pointer :: c_str(:) + + c_str_ptr = stdlib_get_cwd(len, stat) + + if (stat /= 0) then + err0 = state_type(STDLIB_FS_ERROR, "code: ", to_string(stat)//",", c_get_strerror()) + call err0%handle(err) + end if + + call c_f_pointer(c_str_ptr, c_str, [len]) + + allocate(character(len=len) :: cwd) + + do concurrent (i=1:len) + cwd(i:i) = c_str(i) + end do +end subroutine get_cwd + +subroutine set_cwd(path, err) + character(len=*), intent(in) :: path + type(state_type), intent(out) :: err + type(state_type) :: err0 + + interface + integer function stdlib_set_cwd(path) bind(C, name='stdlib_set_cwd') + import c_char + character(kind=c_char), intent(in) :: path(*) + end function stdlib_set_cwd + end interface + + integer :: code + + code = stdlib_set_cwd(to_c_char(trim(path))) + + if (code /= 0) then + err0 = state_type(STDLIB_FS_ERROR, "code: ", to_string(code)//",", c_get_strerror()) + call err0%handle(err) + end if +end subroutine set_cwd + !> Returns the file path of the null device for the current operating system. !> !> Version: Helper function. diff --git a/src/stdlib_system.c b/src/stdlib_system.c new file mode 100644 index 000000000..280a3f880 --- /dev/null +++ b/src/stdlib_system.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif /* ifdef _WIN32 */ + +char* stdlib_strerror(size_t* len){ + char* err = strerror(errno); + *len = strlen(err); + return err; +} + +int stdlib_make_directory(const char* path, mode_t mode){ + int code; +#ifdef _WIN32 + code = _mkdir(path); +#else + code = mkdir(path, mode); +#endif /* ifdef _WIN32 */ + + if (!code){ + return 0; + } + + return errno; +} + +int stdlib_remove_directory(const char* path){ + int code; +#ifdef _WIN32 + code = _rmdir(path); +#else + code = rmdir(path); +#endif /* ifdef _WIN32 */ + + if (!code){ + return 0; + } + + return errno; +} + +char* stdlib_get_cwd(size_t* len, int* stat){ + *stat = 0; +#ifdef _WIN32 + char* buffer; + buffer = _getcwd(NULL, 0); + + if (buffer == NULL) { + *stat = errno; + return NULL; + } + + *len = strlen(buffer); + return buffer; +#else + char buffer[PATH_MAX + 1]; + if (!getcwd(buffer, sizeof(buffer))) { + *stat = errno; + } + + *len = strlen(buffer); + + char* res = malloc(*len); + strncpy(res, buffer, *len); + + return res; +#endif /* ifdef _WIN32 */ +} + +int stdlib_set_cwd(char* path) { + int code; +#ifdef _WIN32 + code = _chdir(path); +#else + code = chdir(path); +#endif /* ifdef _WIN32 */ + + if (code == -1) return errno; + return 0; +} diff --git a/test/system/test_filesystem.f90 b/test/system/test_filesystem.f90 index 4cf1690e4..add6a9323 100644 --- a/test/system/test_filesystem.f90 +++ b/test/system/test_filesystem.f90 @@ -1,6 +1,6 @@ module test_filesystem use testdrive, only : new_unittest, unittest_type, error_type, check, skip_test - use stdlib_system, only: is_directory, delete_file + use stdlib_system, only: is_directory, delete_file, make_directory, remove_directory use stdlib_error, only: state_type implicit none @@ -17,7 +17,11 @@ subroutine collect_suite(testsuite) new_unittest("fs_is_directory_file", test_is_directory_file), & new_unittest("fs_delete_non_existent", test_delete_file_non_existent), & new_unittest("fs_delete_existing_file", test_delete_file_existing), & - new_unittest("fs_delete_file_being_dir", test_delete_directory) & + new_unittest("fs_delete_file_being_dir", test_delete_directory), & + new_unittest("fs_make_dir", test_make_directory), & + new_unittest("fs_make_dir_existing_dir", test_make_directory_existing), & + new_unittest("fs_remove_dir", test_remove_directory), & + new_unittest("fs_remove_dir_non_existent", test_remove_directory_nonexistent) & ] end subroutine collect_suite @@ -145,7 +149,81 @@ subroutine test_delete_directory(error) if (allocated(error)) return end subroutine test_delete_directory + + subroutine test_make_directory(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + character(len=256) :: filename + integer :: ios,iocmd + character(len=512) :: msg + + filename = "test_directory" + + call make_directory(filename, err=err) + call check(error, err%ok(), 'Could not make directory: '//err%print()) + if (allocated(error)) return + + ! Clean up: remove the empty directory + call execute_command_line('rmdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, 'Cannot cleanup make_directory test: '//trim(msg)) + end subroutine test_make_directory + subroutine test_make_directory_existing(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + character(len=256) :: filename + integer :: ios,iocmd + character(len=512) :: msg + + filename = "test_directory" + + call execute_command_line('mkdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, 'Cannot init make_directory_existing test: '//trim(msg)) + if (allocated(error)) return + + call make_directory(filename, err=err) + call check(error, err%error(), 'Made an already existing directory somehow') + + ! Clean up: remove the empty directory + call execute_command_line('rmdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + if (allocated(error)) then + ! if previous error is allocated as well + call check(error, ios==0 .and. iocmd==0, error%message // ' and cannot cleanup make_directory test: '//trim(msg)) + return + end if + call check(error, ios==0 .and. iocmd==0, 'Cannot cleanup make_directory test: '//trim(msg)) + end subroutine test_make_directory_existing + + subroutine test_remove_directory(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + character(len=256) :: filename + integer :: ios,iocmd + character(len=512) :: msg + + filename = "test_directory" + + call execute_command_line('mkdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, 'Cannot init remove_directory test: '//trim(msg)) + if (allocated(error)) return + + call remove_directory(filename, err) + call check(error, err%ok(), 'Could not remove directory: '//err%print()) + if (allocated(error)) then + ! Clean up: remove the empty directory + call execute_command_line('rmdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, error%message // ' and cannot cleanup make_directory test: '//trim(msg)) + end if + end subroutine test_remove_directory + + subroutine test_remove_directory_nonexistent(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + + call remove_directory("random_name", err) + call check(error, err%error(), 'Somehow removed a non-existent directory!: ') + if (allocated(error)) return + end subroutine test_remove_directory_nonexistent end module test_filesystem