@@ -5,7 +5,7 @@ module ferp_dir |
| 5 | implicit none | 5 | implicit none |
| 6 | private | 6 | private |
| 7 | | 7 | |
| 8 | - public :: is_directory, is_regular_file, collect_files | 8 | + public :: is_directory, is_regular_file, is_symlink, collect_files |
| 9 | public :: glob_match | 9 | public :: glob_match |
| 10 | public :: read_patterns_from_file, matches_any_pattern | 10 | public :: read_patterns_from_file, matches_any_pattern |
| 11 | | 11 | |
@@ -89,6 +89,35 @@ contains |
| 89 | | 89 | |
| 90 | end function is_regular_file | 90 | end function is_regular_file |
| 91 | | 91 | |
| | 92 | + function is_symlink(path) result(is_link) |
| | 93 | + !> Check if path is a symbolic link using lstat |
| | 94 | + character(len=*), intent(in) :: path |
| | 95 | + logical :: is_link |
| | 96 | + |
| | 97 | + character(len=max_path_len+1) :: c_path |
| | 98 | + character(len=STAT_BUF_SIZE), target :: statbuf |
| | 99 | + integer(c_int) :: istat |
| | 100 | + integer :: mode_offset, mode_val |
| | 101 | + |
| | 102 | + is_link = .false. |
| | 103 | + |
| | 104 | + c_path = trim(path) // c_null_char |
| | 105 | + istat = c_lstat(c_path, c_loc(statbuf)) |
| | 106 | + |
| | 107 | + if (istat /= 0) return |
| | 108 | + |
| | 109 | + ! On Linux x86_64, st_mode is at offset 24 (bytes 25-28) |
| | 110 | + ! st_mode is typically uint32_t |
| | 111 | + mode_offset = 24 |
| | 112 | + mode_val = transfer(statbuf(mode_offset+1:mode_offset+4), 0) |
| | 113 | + |
| | 114 | + ! S_IFLNK = 0120000 (octal) = 40960 (decimal) |
| | 115 | + ! The file type is in bits 12-15 of mode |
| | 116 | + ! S_IFMT mask = 0170000 (octal) = 61440 |
| | 117 | + is_link = iand(mode_val, 61440) == 40960 |
| | 118 | + |
| | 119 | + end function is_symlink |
| | 120 | + |
| 92 | subroutine collect_files(start_path, file_list, num_files, recursive, & | 121 | subroutine collect_files(start_path, file_list, num_files, recursive, & |
| 93 | follow_links, include_globs, num_include, & | 122 | follow_links, include_globs, num_include, & |
| 94 | exclude_globs, num_exclude, exclude_dirs, num_exclude_dirs) | 123 | exclude_globs, num_exclude, exclude_dirs, num_exclude_dirs) |
@@ -159,6 +188,9 @@ contains |
| 159 | entry_path = trim(current_dir) // '/' // trim(entry_name) | 188 | entry_path = trim(current_dir) // '/' // trim(entry_name) |
| 160 | end if | 189 | end if |
| 161 | | 190 | |
| | 191 | + ! Skip symlinks if not following them (like grep -r vs grep -R) |
| | 192 | + if (.not. follow_links .and. is_symlink(entry_path)) cycle |
| | 193 | + |
| 162 | ! Check if it's a directory | 194 | ! Check if it's a directory |
| 163 | if (is_directory(entry_path)) then | 195 | if (is_directory(entry_path)) then |
| 164 | if (recursive) then | 196 | if (recursive) then |