Read a record header, check the type tag, and bounds check the announced payload against the real length of the file.
Comparing the payload with inquire(size=) before allocating is what
turns a truncated or corrupt file into a clean ERROR_IO instead of a
wild allocation followed by a short read.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer(kind=default_int), | intent(in) | :: | unit | |||
| integer(kind=int32), | intent(in) | :: | expected_tag | |||
| logical, | intent(in) | :: | swapped | |||
| integer(kind=int64), | intent(out) | :: | count | |||
| logical, | intent(out) | :: | ok | |||
| type(error_t), | intent(inout) | :: | err |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer(kind=int64), | private | :: | available | ||||
| integer(kind=int64), | private | :: | cur_pos | ||||
| integer(kind=int64), | private | :: | esz | ||||
| integer(kind=int64), | private | :: | file_size | ||||
| integer(kind=default_int), | private | :: | ios | ||||
| integer(kind=int64), | private | :: | raw_count | ||||
| integer(kind=int32), | private | :: | tag |
subroutine read_record_header(unit, expected_tag, swapped, count, ok, err) !! Read a record header, check the type tag, and bounds check the !! announced payload against the real length of the file. !! !! Comparing the payload with `inquire(size=)` before allocating is what !! turns a truncated or corrupt file into a clean `ERROR_IO` instead of a !! wild allocation followed by a short read. integer(default_int), intent(in) :: unit integer(int32), intent(in) :: expected_tag logical, intent(in) :: swapped integer(int64), intent(out) :: count logical, intent(out) :: ok type(error_t), intent(inout) :: err integer(default_int) :: ios integer(int32) :: tag integer(int64) :: raw_count, esz, file_size, cur_pos, available count = 0_int64 ok = .false. read (unit, iostat=ios) tag call check_io(ios, "unexpected end of stream reading record tag", err) if (ios /= 0) return if (swapped) tag = bswap_int32(tag) if (tag /= expected_tag) then call err%set(ERROR_VALIDATION, "record type mismatch: expected tag "//to_char(expected_tag)// & ", found "//to_char(tag)) return end if read (unit, iostat=ios) raw_count call check_io(ios, "unexpected end of stream reading record length", err) if (ios /= 0) return if (swapped) raw_count = bswap_int64(raw_count) if (raw_count < 0_int64) then call err%set(ERROR_VALIDATION, "negative record element count "//to_char(raw_count)) return end if inquire (unit, size=file_size, pos=cur_pos, iostat=ios) call check_io(ios, "cannot inquire stream size", err) if (ios /= 0) return esz = element_bytes(expected_tag) available = file_size - (cur_pos - 1_int64) ! Divide instead of multiplying so an absurd count cannot overflow int64. if (raw_count > available/esz) then call err%set(ERROR_IO, "truncated record: "//to_char(raw_count)//" elements announced but only "// & to_char(available)//" payload bytes remain") return end if count = raw_count ok = .true. end subroutine read_record_header