| 1 | ! ============================================================================== |
| 2 | ! Module: error_handling |
| 3 | ! Purpose: Comprehensive error handling and logging for fortsh |
| 4 | ! ============================================================================== |
| 5 | module error_handling |
| 6 | use iso_fortran_env, only: output_unit, error_unit |
| 7 | implicit none |
| 8 | |
| 9 | ! Error severity levels |
| 10 | integer, parameter :: ERR_DEBUG = 0 |
| 11 | integer, parameter :: ERR_INFO = 1 |
| 12 | integer, parameter :: ERR_WARN = 2 |
| 13 | integer, parameter :: ERR_ERROR = 3 |
| 14 | integer, parameter :: ERR_FATAL = 4 |
| 15 | |
| 16 | ! Error categories |
| 17 | integer, parameter :: ERR_CAT_PARSER = 100 |
| 18 | integer, parameter :: ERR_CAT_EXECUTOR = 200 |
| 19 | integer, parameter :: ERR_CAT_SYSTEM = 300 |
| 20 | integer, parameter :: ERR_CAT_IO = 400 |
| 21 | integer, parameter :: ERR_CAT_MEMORY = 500 |
| 22 | |
| 23 | ! Global error handling state |
| 24 | logical :: debug_mode = .false. |
| 25 | logical :: verbose_errors = .true. |
| 26 | integer :: max_error_count = 50 |
| 27 | integer :: error_count = 0 |
| 28 | |
| 29 | type :: error_info_t |
| 30 | integer :: severity |
| 31 | integer :: category |
| 32 | integer :: code |
| 33 | character(len=256) :: message |
| 34 | character(len=64) :: location |
| 35 | character(len=32) :: timestamp |
| 36 | end type error_info_t |
| 37 | |
| 38 | type(error_info_t) :: error_history(50) |
| 39 | |
| 40 | contains |
| 41 | |
| 42 | ! Log an error with context information |
| 43 | subroutine log_error(severity, category, code, message, location) |
| 44 | integer, intent(in) :: severity, category, code |
| 45 | character(len=*), intent(in) :: message |
| 46 | character(len=*), intent(in), optional :: location |
| 47 | |
| 48 | character(len=32) :: severity_str, category_str |
| 49 | character(len=64) :: loc_str |
| 50 | |
| 51 | ! Increment error count and store in history |
| 52 | error_count = error_count + 1 |
| 53 | if (error_count <= max_error_count) then |
| 54 | error_history(error_count)%severity = severity |
| 55 | error_history(error_count)%category = category |
| 56 | error_history(error_count)%code = code |
| 57 | error_history(error_count)%message = message |
| 58 | if (present(location)) then |
| 59 | error_history(error_count)%location = location |
| 60 | else |
| 61 | error_history(error_count)%location = 'unknown' |
| 62 | end if |
| 63 | call get_timestamp(error_history(error_count)%timestamp) |
| 64 | end if |
| 65 | |
| 66 | ! Only display if severity is high enough |
| 67 | if (severity < ERR_WARN .and. .not. debug_mode) return |
| 68 | |
| 69 | ! Format severity and category |
| 70 | call format_severity(severity, severity_str) |
| 71 | call format_category(category, category_str) |
| 72 | |
| 73 | if (present(location)) then |
| 74 | loc_str = location |
| 75 | else |
| 76 | loc_str = 'unknown' |
| 77 | end if |
| 78 | |
| 79 | ! Print error message |
| 80 | if (severity >= ERR_ERROR) then |
| 81 | write(error_unit, '(a,a,a,a,a,i15,a,a,a,a,a)') & |
| 82 | '[', trim(severity_str), '] ', trim(category_str), ' (', code, ') in ', & |
| 83 | trim(loc_str), ': ', trim(message) |
| 84 | else if (verbose_errors .or. debug_mode) then |
| 85 | write(output_unit, '(a,a,a,a,a,i15,a,a,a,a,a)') & |
| 86 | '[', trim(severity_str), '] ', trim(category_str), ' (', code, ') in ', & |
| 87 | trim(loc_str), ': ', trim(message) |
| 88 | end if |
| 89 | |
| 90 | ! Fatal errors should terminate |
| 91 | if (severity == ERR_FATAL) then |
| 92 | write(error_unit, '(a)') 'FATAL ERROR: Terminating shell' |
| 93 | stop 1 |
| 94 | end if |
| 95 | end subroutine |
| 96 | |
| 97 | ! Convenience functions for common error types |
| 98 | subroutine parser_error(code, message, location) |
| 99 | integer, intent(in) :: code |
| 100 | character(len=*), intent(in) :: message |
| 101 | character(len=*), intent(in), optional :: location |
| 102 | call log_error(ERR_ERROR, ERR_CAT_PARSER, code, message, location) |
| 103 | end subroutine |
| 104 | |
| 105 | subroutine executor_error(code, message, location) |
| 106 | integer, intent(in) :: code |
| 107 | character(len=*), intent(in) :: message |
| 108 | character(len=*), intent(in), optional :: location |
| 109 | call log_error(ERR_ERROR, ERR_CAT_EXECUTOR, code, message, location) |
| 110 | end subroutine |
| 111 | |
| 112 | subroutine system_error(code, message, location) |
| 113 | integer, intent(in) :: code |
| 114 | character(len=*), intent(in) :: message |
| 115 | character(len=*), intent(in), optional :: location |
| 116 | call log_error(ERR_ERROR, ERR_CAT_SYSTEM, code, message, location) |
| 117 | end subroutine |
| 118 | |
| 119 | subroutine io_error(code, message, location) |
| 120 | integer, intent(in) :: code |
| 121 | character(len=*), intent(in) :: message |
| 122 | character(len=*), intent(in), optional :: location |
| 123 | call log_error(ERR_ERROR, ERR_CAT_IO, code, message, location) |
| 124 | end subroutine |
| 125 | |
| 126 | subroutine memory_error(code, message, location) |
| 127 | integer, intent(in) :: code |
| 128 | character(len=*), intent(in) :: message |
| 129 | character(len=*), intent(in), optional :: location |
| 130 | call log_error(ERR_FATAL, ERR_CAT_MEMORY, code, message, location) |
| 131 | end subroutine |
| 132 | |
| 133 | subroutine debug_log(message, location) |
| 134 | character(len=*), intent(in) :: message |
| 135 | character(len=*), intent(in), optional :: location |
| 136 | call log_error(ERR_DEBUG, 0, 0, message, location) |
| 137 | end subroutine |
| 138 | |
| 139 | subroutine warning_log(message, location) |
| 140 | character(len=*), intent(in) :: message |
| 141 | character(len=*), intent(in), optional :: location |
| 142 | call log_error(ERR_WARN, 0, 0, message, location) |
| 143 | end subroutine |
| 144 | |
| 145 | ! Validate system resource availability |
| 146 | function check_system_resources() result(is_ok) |
| 147 | logical :: is_ok |
| 148 | integer :: available_memory, available_fds |
| 149 | |
| 150 | is_ok = .true. |
| 151 | |
| 152 | ! Basic resource checks (simplified) |
| 153 | available_memory = 1000000 ! Placeholder |
| 154 | available_fds = 100 ! Placeholder |
| 155 | |
| 156 | if (available_memory < 1000) then |
| 157 | call system_error(301, 'Low memory warning', 'check_system_resources') |
| 158 | is_ok = .false. |
| 159 | end if |
| 160 | |
| 161 | if (available_fds < 10) then |
| 162 | call system_error(302, 'Low file descriptor count', 'check_system_resources') |
| 163 | is_ok = .false. |
| 164 | end if |
| 165 | end function |
| 166 | |
| 167 | ! Validate command before execution |
| 168 | function validate_command(command) result(is_valid) |
| 169 | character(len=*), intent(in) :: command |
| 170 | logical :: is_valid |
| 171 | |
| 172 | is_valid = .true. |
| 173 | |
| 174 | ! Basic command validation |
| 175 | if (len_trim(command) == 0) then |
| 176 | call executor_error(201, 'Empty command', 'validate_command') |
| 177 | is_valid = .false. |
| 178 | return |
| 179 | end if |
| 180 | |
| 181 | if (len_trim(command) > 4096) then |
| 182 | call executor_error(202, 'Command too long', 'validate_command') |
| 183 | is_valid = .false. |
| 184 | return |
| 185 | end if |
| 186 | |
| 187 | ! Check for potentially dangerous commands |
| 188 | if (index(command, 'rm -rf /') > 0) then |
| 189 | call executor_error(203, 'Dangerous command detected', 'validate_command') |
| 190 | is_valid = .false. |
| 191 | return |
| 192 | end if |
| 193 | |
| 194 | call debug_log('Command validation passed: ' // trim(command), 'validate_command') |
| 195 | end function |
| 196 | |
| 197 | ! Validate file operations |
| 198 | function validate_file_operation(operation, filepath) result(is_valid) |
| 199 | character(len=*), intent(in) :: operation, filepath |
| 200 | logical :: is_valid |
| 201 | |
| 202 | is_valid = .true. |
| 203 | |
| 204 | if (len_trim(filepath) == 0) then |
| 205 | call io_error(401, 'Empty file path', 'validate_file_operation') |
| 206 | is_valid = .false. |
| 207 | return |
| 208 | end if |
| 209 | |
| 210 | if (len_trim(filepath) > 4096) then |
| 211 | call io_error(402, 'File path too long', 'validate_file_operation') |
| 212 | is_valid = .false. |
| 213 | return |
| 214 | end if |
| 215 | |
| 216 | ! Check for directory traversal attempts |
| 217 | if (index(filepath, '../') > 0) then |
| 218 | call warning_log('Directory traversal detected: ' // trim(filepath), 'validate_file_operation') |
| 219 | end if |
| 220 | |
| 221 | call debug_log('File operation validated: ' // trim(operation) // ' ' // trim(filepath), & |
| 222 | 'validate_file_operation') |
| 223 | end function |
| 224 | |
| 225 | ! Memory allocation wrapper with error handling |
| 226 | subroutine safe_allocate_string_array(array, size, length, location) |
| 227 | character(len=:), allocatable, intent(out) :: array(:) |
| 228 | integer, intent(in) :: size, length |
| 229 | character(len=*), intent(in), optional :: location |
| 230 | |
| 231 | integer :: stat |
| 232 | character(len=64) :: loc_str |
| 233 | |
| 234 | if (present(location)) then |
| 235 | loc_str = location |
| 236 | else |
| 237 | loc_str = 'unknown' |
| 238 | end if |
| 239 | |
| 240 | allocate(character(len=length) :: array(size), stat=stat) |
| 241 | |
| 242 | if (stat /= 0) then |
| 243 | call memory_error(501, 'Failed to allocate string array', loc_str) |
| 244 | else |
| 245 | call debug_log('Successfully allocated string array', loc_str) |
| 246 | end if |
| 247 | end subroutine |
| 248 | |
| 249 | ! Print error summary |
| 250 | subroutine print_error_summary() |
| 251 | integer :: i, warn_count, error_count_local, fatal_count |
| 252 | |
| 253 | warn_count = 0 |
| 254 | error_count_local = 0 |
| 255 | fatal_count = 0 |
| 256 | |
| 257 | do i = 1, min(error_count, max_error_count) |
| 258 | select case(error_history(i)%severity) |
| 259 | case(ERR_WARN) |
| 260 | warn_count = warn_count + 1 |
| 261 | case(ERR_ERROR) |
| 262 | error_count_local = error_count_local + 1 |
| 263 | case(ERR_FATAL) |
| 264 | fatal_count = fatal_count + 1 |
| 265 | end select |
| 266 | end do |
| 267 | |
| 268 | if (error_count > 0) then |
| 269 | write(output_unit, '(a)') '' |
| 270 | write(output_unit, '(a)') 'Error Summary:' |
| 271 | write(output_unit, '(a,i15)') ' Warnings: ', warn_count |
| 272 | write(output_unit, '(a,i15)') ' Errors: ', error_count_local |
| 273 | write(output_unit, '(a,i15)') ' Fatal: ', fatal_count |
| 274 | write(output_unit, '(a,i15)') ' Total: ', min(error_count, max_error_count) |
| 275 | end if |
| 276 | end subroutine |
| 277 | |
| 278 | ! Clear error history |
| 279 | subroutine clear_error_history() |
| 280 | error_count = 0 |
| 281 | call debug_log('Error history cleared', 'clear_error_history') |
| 282 | end subroutine |
| 283 | |
| 284 | ! Set debugging mode |
| 285 | subroutine set_debug_mode(enabled) |
| 286 | logical, intent(in) :: enabled |
| 287 | debug_mode = enabled |
| 288 | if (enabled) then |
| 289 | call debug_log('Debug mode enabled', 'set_debug_mode') |
| 290 | end if |
| 291 | end subroutine |
| 292 | |
| 293 | ! Helper functions |
| 294 | subroutine format_severity(severity, str) |
| 295 | integer, intent(in) :: severity |
| 296 | character(len=*), intent(out) :: str |
| 297 | |
| 298 | select case(severity) |
| 299 | case(ERR_DEBUG) |
| 300 | str = 'DEBUG' |
| 301 | case(ERR_INFO) |
| 302 | str = 'INFO' |
| 303 | case(ERR_WARN) |
| 304 | str = 'WARN' |
| 305 | case(ERR_ERROR) |
| 306 | str = 'ERROR' |
| 307 | case(ERR_FATAL) |
| 308 | str = 'FATAL' |
| 309 | case default |
| 310 | str = 'UNKNOWN' |
| 311 | end select |
| 312 | end subroutine |
| 313 | |
| 314 | subroutine format_category(category, str) |
| 315 | integer, intent(in) :: category |
| 316 | character(len=*), intent(out) :: str |
| 317 | |
| 318 | select case(category) |
| 319 | case(ERR_CAT_PARSER) |
| 320 | str = 'PARSER' |
| 321 | case(ERR_CAT_EXECUTOR) |
| 322 | str = 'EXECUTOR' |
| 323 | case(ERR_CAT_SYSTEM) |
| 324 | str = 'SYSTEM' |
| 325 | case(ERR_CAT_IO) |
| 326 | str = 'IO' |
| 327 | case(ERR_CAT_MEMORY) |
| 328 | str = 'MEMORY' |
| 329 | case default |
| 330 | str = 'GENERAL' |
| 331 | end select |
| 332 | end subroutine |
| 333 | |
| 334 | subroutine get_timestamp(timestamp) |
| 335 | character(len=*), intent(out) :: timestamp |
| 336 | ! Simplified timestamp - in production would use system calls |
| 337 | timestamp = '2024-01-01T12:00:00' |
| 338 | end subroutine |
| 339 | |
| 340 | end module error_handling |