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 13 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
50 changes: 50 additions & 0 deletions doc/specs/stdlib_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,56 @@ Subroutine

---

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

### Status

Experimental

### Description

This function makes a system call (syscall) to retrieve metadata for the specified path and determines its type.
It can distinguish between the following path types:

- Regular File
- Directory
- Symbolic Link

It returns a constant representing the detected path type, or `type_unknown` if the type cannot be determined.
Any encountered errors are handled using `state_type`.

### Syntax

`fs_type = [[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

`fs_type`: An `intent(out), integer` parameter indicating the type. The possible values are:
- `fs_type_unknown`: 0 => an unknown type
- `fs_type_regular_file`: 1 => a regular file
- `fs_type_directory`: 2 => a directory
- `fs_type_symlink`: 3 => a symbolic link

`err`(optional): It is an optional state return flag. If not requested and an error occurs, 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)
30 changes: 30 additions & 0 deletions example/system/example_exists.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
! Illustrate the usage of `exists`
program example_exists
use stdlib_system, only: exists, fs_type_unknown, fs_type_regular_file, &
fs_type_directory, fs_type_symlink
use stdlib_error, only: state_type
implicit none

type(state_type) :: err

! Path to check
character(*), parameter :: path = "path"
! To get the type of the path
integer :: t

t = exists(path, err)

if (err%error()) then
! An error occured, print it
print *, err%print()
end if

! switching on the type returned by `exists`
select case (t)
case (fs_type_unknown); print *, "Unknown type!"
case (fs_type_regular_file); print *, "Regular File!"
case (fs_type_directory); print *, "Directory!"
case (fs_type_symlink); print *, "Symbolic Link!"
end select
end program example_exists

106 changes: 99 additions & 7 deletions src/stdlib_system.F90
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,46 @@ 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
fs_type_unknown = 0, &
!> Represents a regular file
fs_type_regular_file = 1, &
!> Represents a directory
fs_type_directory = 2, &
!> Represents a symbolic link
fs_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
!!
!! This function makes a system call (syscall) to retrieve metadata for the specified path and determines its type.
!! It can distinguish between the following path types:
!!
!! - Regular File
!! - Directory
!! - Symbolic Link
!!
!! It returns a constant representing the detected path type, or `type_unknown` if the type cannot be determined.
!! Any encountered errors are handled using `state_type`.
!!
public :: exists

public :: is_symlink
public :: is_regular_file

! CPU clock ticks storage
integer, parameter, private :: TICKS = int64
Expand Down Expand Up @@ -896,25 +936,30 @@ end function stdlib_is_directory

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)
! A helper function to get the string describing an error from C functions.
! If `winapi` is false or not present, uses `strerror` provided by `<string.h>`
! Otherwise, uses `strerror` on unix and `FormatMessageA` on windows.
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_

winapi_ = optval(winapi, .false.)

c_str_ptr = strerror(len)
c_str_ptr = strerror(len, winapi_)

call c_f_pointer(c_str_ptr, c_str, [len])

Expand Down Expand Up @@ -1134,6 +1179,53 @@ 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

! checks if a path exists and returns its type
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(*)
! to return the error code if any
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)

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

logical function is_symlink(path)
character(len=*), intent(in) :: path

is_symlink = exists(path) == fs_type_symlink
end function is_symlink

logical function is_regular_file(path)
character(len=*), intent(in) :: path

interface
logical(c_bool) function stdlib_is_regular_file(path) bind(C, name='stdlib_is_regular_file')
import c_char, c_bool
character(kind=c_char) :: path(:)
end function stdlib_is_regular_file
end interface

is_regular_file = logical(stdlib_is_regular_file(to_c_char(path)))
end function is_regular_file

character function path_sep()
if (OS_TYPE() == OS_WINDOWS) then
path_sep = '\'
Expand Down
91 changes: 89 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.
// Always Uses `strerr` on unix.
// if `winapi` is `false`, uses the usual `strerr` on windows.
// If `winapi` is `true`, uses `FormatMessageA`(from windows.h) on windows.
char* stdlib_strerror(size_t* len, bool winapi){

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

FormatMessageA(
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,62 @@ 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 fs_type_unknown = 0;
const int fs_type_regular_file = 1;
const int fs_type_directory = 2;
const int fs_type_symlink = 3;

int type = fs_type_unknown;
*stat = 0;

#ifdef _WIN32
DWORD attrs = GetFileAttributesA(path);

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

// Let's assume it is a regular file
type = fs_type_regular_file;

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

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

switch (buf.st_mode & S_IFMT) {
case S_IFREG: type = fs_type_regular_file; break;
case S_IFDIR: type = fs_type_directory; break;
case S_IFLNK: type = fs_type_symlink; break;
default: type = fs_type_unknown; break;
}
#endif /* ifdef _WIN32 */
return type;
}

// `stat` and `_stat` follow symlinks automatically.
// so no need for winapi functions.
bool stdlib_is_regular_file(const char* path) {
#ifdef _WIN32
struct _stat buf = {0};
return _stat(path, &buf) == 0 && S_ISREG(buf.st_mode);
#else
struct stat buf = {0};
return stat(path, &buf) == 0 && S_ISREG(buf.st_mode);
#endif /* ifdef _WIN32 */
}
Loading
Loading