Skip to content

feat: exists to return the type of a path #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions doc/specs/stdlib_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,51 @@ Subroutine

---

## `exists` - Checks if a path exists in the filesystem

### Status

Experimental

### Description

This function performs a system call (syscall) to the operating system, to retrieve the metadata
corresponding to the path, and identifies the type of path it is.
It can distinguish among the following path types

- Regular File
- Directory
- Symbolic Link

Returns a constant representing the path type or `type_unknown` if it cannot be determined.
If there has been an error, It is handled using `state_type`.

### Syntax

`res = [[stdlib_system(module):exists(function)]] (path [, err])`

### Class

Function

### Arguments

`path`: Shall be a character string containing the path. It is an `intent(in)` argument.

`err`(optional): Shall be of type `state_type`, and is used for error handling. It is an `optional, intent(out)` argument.

### Return values

`err` is an optional state return flag. On error if not requested, an `FS_ERROR` will trigger an error stop.

### Example

```fortran
{!example/system/example_exists.f90!}
```

---

## `null_device` - Return the null device file path

### Status
Expand Down
1 change: 1 addition & 0 deletions example/system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ ADD_EXAMPLE(path_base_name)
ADD_EXAMPLE(path_dir_name)
ADD_EXAMPLE(make_directory)
ADD_EXAMPLE(remove_directory)
ADD_EXAMPLE(exists)
26 changes: 26 additions & 0 deletions example/system/example_exists.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
! Illustrate the usage of `exists`
program example_exists
use stdlib_system, only: exists, type_unknown, type_regular_file, &
type_directory, type_symlink
use stdlib_error, only: state_type
implicit none

type(state_type) :: err

character(*), parameter :: path = "path"
integer :: t

t = exists(path, err)

if (err%error()) then
print *, err%print()
end if

select case (t)
case (type_unknown); print *, "Unknown type!"
case (type_regular_file); print *, "Regular File!"
case (type_directory); print *, "Directory!"
case (type_symlink); print *, "Symbolic Link!"
end select
end program example_exists

76 changes: 72 additions & 4 deletions src/stdlib_system.F90
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,44 @@ module stdlib_system
!! ([Specification](../page/specs/stdlib_system.html#FS_ERROR_CODE))
!!
public :: FS_ERROR_CODE

!> Version: experimental
!>
!> Integer constants representing the most common path types.
!> ([Specification](../page/specs/stdlib_system.html))
integer, parameter, public :: &
!> Represents an unknown path type
type_unknown = 0, &
!> Represents a regular file
type_regular_file = 1, &
!> Represents a directory
type_directory = 2, &
!> Represents a symbolic link
type_symlink = 3

!! version: experimental
!!
!! Checks if a path exists in the filesystem.
!! ([Specification](../page/specs/stdlib_system.html#exists))
!!
!!### Summary
!! Function to check whether the path exists in the fileystem at all.
!! If the path does exist, returns the type of the path.
!!
!!### Description
!!
!! The function performs a system call (syscall) to the operating system, to retrieve the metadata
!! corresponding to a path, and identifies the type of path it is.
!! It can distinguish among the following path types
!!
!! - Regular File
!! - Directory
!! - Symbolic Link
!!
!! Returns a constant representing the path type or `type_unknown` if it cannot be determined.
!! If there has been an error, It is handled using `state_type`.
!!
public :: exists

! CPU clock ticks storage
integer, parameter, private :: TICKS = int64
Expand Down Expand Up @@ -899,22 +937,27 @@ end function is_directory
! A helper function to get the result of the C function `strerror`.
! `strerror` is a function provided by `<string.h>`.
! It returns a string describing the meaning of `errno` in the C header `<errno.h>`
function c_get_strerror() result(str)
function c_get_strerror(winapi) result(str)
character(len=:), allocatable :: str
logical, optional, intent(in) :: winapi

interface
type(c_ptr) function strerror(len) bind(C, name='stdlib_strerror')
import c_size_t, c_ptr
type(c_ptr) function strerror(len, winapi) bind(C, name='stdlib_strerror')
import c_size_t, c_ptr, c_bool
implicit none
integer(c_size_t), intent(out) :: len
logical, intent(in) :: winapi
end function strerror
end interface

type(c_ptr) :: c_str_ptr
integer(c_size_t) :: len, i
character(kind=c_char), pointer :: c_str(:)
logical :: winapi_

c_str_ptr = strerror(len)
winapi_ = optval(winapi, .false.)

c_str_ptr = strerror(len, winapi_)

call c_f_pointer(c_str_ptr, c_str, [len])

Expand Down Expand Up @@ -1134,6 +1177,31 @@ pure function FS_ERROR(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,&
a13,a14,a15,a16,a17,a18,a19,a20)
end function FS_ERROR

function exists(path, err) result(fs_type)
character(*), intent(in) :: path
type(state_type), optional, intent(out) :: err
integer :: fs_type

type(state_type) :: err0

interface
integer function stdlib_exists(path, stat) bind(C, name='stdlib_exists')
import c_char, c_int
character(kind=c_char), intent(in) :: path(*)
integer(kind=c_int), intent(out) :: stat
end function stdlib_exists
end interface

integer(kind=c_int) :: stat

fs_type = stdlib_exists(to_c_char(trim(path)), stat)

if (stat /= 0) then
err0 = FS_ERROR_CODE(stat, c_get_strerror())
call err0%handle(err)
end if
end function exists

character function path_sep()
if (OS_TYPE() == OS_WINDOWS) then
path_sep = '\'
Expand Down
79 changes: 77 additions & 2 deletions src/stdlib_system.c
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
#include <stdbool.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#ifdef _WIN32
#include <direct.h>
#include <windows.h>
#else
#include <unistd.h>
#endif /* ifdef _WIN32 */

// Returns the string describing the meaning of `errno` code (by calling `strerror`).
char* stdlib_strerror(size_t* len){
// Wrapper to get the string describing a system syscall error.
// Uses `strerr` on unix.
// if `winapi` is `false`, uses the usual `strerr` on windows.
// If `winapi` is `false`, uses `FormatMessage`(from windows.h) on windows.
char* stdlib_strerror(size_t* len, bool winapi){

if (winapi) {
#ifdef _WIN32
LPSTR err = NULL;
DWORD dw = GetLastError();

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &err,
0,
NULL);

*len = strlen(err);
return (char*) err;

#endif /* ifdef _WIN32 */
}

char* err = strerror(errno);
*len = strlen(err);
return err;
Expand Down Expand Up @@ -44,3 +72,50 @@ int stdlib_remove_directory(const char* path){

return (!code) ? 0 : errno;
}

// Wrapper to the platform's `stat`(status of path) call.
// Uses `lstat` on unix, `GetFileAttributesA` on windows.
// Returns the `type` of the path, and sets the `stat`(if any errors).
int stdlib_exists(const char* path, int* stat){
// All the valid types
const int type_unknown = 0;
const int type_regular_file = 1;
const int type_directory = 2;
const int type_symlink = 3;

int type = type_unknown;
*stat = 0;

#ifdef _WIN32
DWORD attrs = GetFileAttributesA(path);

if (attrs == INVALID_FILE_ATTRIBUTES) {
*stat = (int) GetLastError();
return type_unknown;
}

// It is not a directory or a symlink
type = type_regular_file;

if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) type = type_symlink;
if (attrs & FILE_ATTRIBUTE_DIRECTORY) type = type_directory;
#else
struct stat buf = {0};
int status;
status = lstat(path, &buf);

if (status == -1) {
// `lstat` failed
*stat = errno;
return type_unknown;
}

switch (buf.st_mode & S_IFMT) {
case S_IFREG: type = type_regular_file; break;
case S_IFDIR: type = type_directory; break;
case S_IFLNK: type = type_symlink; break;
default: type = type_unknown; break;
}
#endif /* ifdef _WIN32 */
return type;
}
Loading
Loading