File System Directories

The Fortran language does not feature routines for direct file system directory access, like reading the contents of a directory.

POSIX

Starting with Fortran 2003, POSIX-conforming ISO C binding interfaces can be implemented easily to access the functions opendir(), readdir(), and closedir(). The returned dirent structure depends on the used operating system and may differ on Linux.

! posix.f90
module posix
    use, intrinsic :: iso_c_binding
    implicit none

    public :: c_closedir
    public :: c_opendir
    public :: c_readdir
    public :: c_f_str_chars

    ! The dirent structure on FreeBSD.
    type, bind(c), public :: c_dirent
        integer(kind=c_int64_t) :: d_fileno
        integer(kind=c_int64_t) :: d_off
        integer(kind=c_int16_t) :: d_reclen
        integer(kind=c_int8_t)  :: d_type
        integer(kind=c_int8_t)  :: d_namlen
        integer(kind=c_int32_t) :: d_pad0
        character(kind=c_char)  :: d_name(256)
    end type c_dirent

    integer(kind=c_int), parameter, public :: DT_UNKNOWN = 0
    integer(kind=c_int), parameter, public :: DT_FIFO    = 1
    integer(kind=c_int), parameter, public :: DT_CHR     = 2
    integer(kind=c_int), parameter, public :: DT_DIR     = 4
    integer(kind=c_int), parameter, public :: DT_BLK     = 6
    integer(kind=c_int), parameter, public :: DT_REG     = 8
    integer(kind=c_int), parameter, public :: DT_LNK     = 10
    integer(kind=c_int), parameter, public :: DT_SOCK    = 12
    integer(kind=c_int), parameter, public :: DT_WHT     = 14

    interface
        ! int closedir(DIR *dirp)
        function c_closedir(dirp) bind(c, name='closedir')
            import :: c_int, c_ptr
            type(c_ptr), intent(in), value :: dirp
            integer(kind=c_int)            :: c_closedir
        end function c_closedir

        ! DIR *opendir(const char *filename)
        function c_opendir(filename) bind(c, name='opendir')
            import :: c_char, c_ptr
            character(kind=c_char), intent(in) :: filename
            type(c_ptr)                        :: c_opendir
        end function c_opendir

        ! struct dirent *readdir(DIR *dirp)
        function c_readdir_(dirp) bind(c, name='readdir')
            import :: c_ptr
            type(c_ptr), intent(in), value :: dirp
            type(c_ptr)                    :: c_readdir_
        end function c_readdir_
    end interface
contains
    function c_readdir(dirp)
        !! Wrapper function that calls `c_readdir()` and converts the returned
        !! C pointer to Fortran pointer.
        type(c_ptr), intent(in) :: dirp
        type(c_dirent), pointer :: c_readdir
        type(c_ptr)             :: ptr

        c_readdir => null()
        ptr = c_readdir_(dirp)

        if (c_associated(ptr)) &
            call c_f_pointer(ptr, c_readdir)
    end function c_readdir

    subroutine c_f_str_chars(c_str, f_str)
        !! Copies a C string, passed as a C char array, to a Fortran string.
        character(len=1, kind=c_char), intent(in)  :: c_str(*)
        character(len=*),              intent(out) :: f_str
        integer                                    :: i

        i = 1

        do while (c_str(i) /= c_null_char .and. i <= len(f_str))
            f_str(i:i) = c_str(i)
            i = i + 1
        end do

        if (i < len(f_str)) &
            f_str(i:) = ' '
    end subroutine c_f_str_chars
end module posix

In the example program, the wrapper function c_readdir() returns the derived type c_dirent, containing the entry name d_name. The C character array must be converted to Fortran string first, using the utility routine c_f_str_chars(). A trailing / is appended to the output if the entry is a directory itself (type DT_DIR).

! example.f90
program main
    use, intrinsic :: iso_fortran_env, only: stderr => error_unit
    use, intrinsic :: iso_c_binding,   only: c_associated, c_null_char, c_ptr
    use :: posix
    implicit none
    character(len=*), parameter :: PATH = '/var/tmp/'
    character(len=256)          :: entry_name
    type(c_dirent), pointer     :: dirent_ptr
    type(c_ptr)                 :: dir_ptr
    integer                     :: rc

    ! Open directory.
    dir_ptr = c_opendir(PATH // c_null_char)

    if (.not. c_associated(dir_ptr)) then
        write (stderr, '("Cannot open directory ", a)') PATH
        stop
    end if

    ! Read in directory entries and output file names.
    do
        ! Get next directory entry.
        dirent_ptr => c_readdir(dir_ptr)

        ! Exit if pointer is null.
        if (.not. associated(dirent_ptr)) &
            exit

        ! Convert C char array to Fortran string.
        call c_f_str_chars(dirent_ptr%d_name, entry_name)

        ! Print entry name.
        select case (dirent_ptr%d_type)
            case (DT_DIR)
                ! Entry is directory.
                print '(2a)', trim(entry_name), '/'
            case default
                print '(a)', trim(entry_name)
        end select
    end do

    ! Close directory.
    rc = c_closedir(dir_ptr)
end program main

We simply compile the module and the example program with GNU Fortran, and then run the binary to print out the contents of directory /var/tmp/ (unsorted).

$ gfortran10 -c posix.f90
$ gfortran10 -o example example.f90 posix.o
$ ./example
./
../
vi.recover/
app.pid
access.log

For sorted output, we can call the POSIX function scandir() instead. But this approach requires us to implement a custom filter function first.