80 changes: 41 additions & 39 deletions rasterio/_io.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ from rasterio._base cimport open_dataset
from rasterio._env import catch_errors
from rasterio._err import (
GDALError, CPLE_AppDefinedError, CPLE_OpenFailedError, CPLE_IllegalArgError, CPLE_BaseError,
CPLE_AWSObjectNotFoundError, CPLE_HttpResponseError)
CPLE_AWSObjectNotFoundError, CPLE_HttpResponseError, stack_errors)
from rasterio.crs import CRS
from rasterio import dtypes
from rasterio.enums import ColorInterp, MaskFlags, Resampling
Expand All @@ -42,7 +42,7 @@ from rasterio.enums import Resampling
from rasterio.env import GDALVersion
from rasterio.errors import ResamplingAlgorithmError, DatasetIOShapeError
from rasterio._base cimport get_driver_name, DatasetBase
from rasterio._err cimport exc_wrap_int, exc_wrap_pointer, exc_wrap_vsilfile
from rasterio._err cimport exc_wrap_int, exc_wrap_pointer, exc_wrap_vsilfile, StackChecker

cimport numpy as np

Expand Down Expand Up @@ -96,6 +96,7 @@ cdef int io_band(GDALRasterBandH band, int mode, double x0, double y0,
cdef int ysize = <int>height
cdef int retval = 3

cdef StackChecker checker
cdef GDALRasterIOExtraArg extras
extras.nVersion = 1
extras.eResampleAlg = <GDALRIOResampleAlg>resampling
Expand All @@ -107,12 +108,12 @@ cdef int io_band(GDALRasterBandH band, int mode, double x0, double y0,
extras.pfnProgress = NULL
extras.pProgressData = NULL

with nogil:
retval = GDALRasterIOEx(
band, <GDALRWFlag>mode, xoff, yoff, xsize, ysize, buf, bufxsize, bufysize,
buftype, bufpixelspace, buflinespace, &extras)

return exc_wrap_int(retval)
with stack_errors() as checker:
with nogil:
retval = GDALRasterIOEx(
band, <GDALRWFlag>mode, xoff, yoff, xsize, ysize, buf, bufxsize, bufysize,
buftype, bufpixelspace, buflinespace, &extras)
return checker.exc_wrap_int(retval)


cdef int io_multi_band(GDALDatasetH hds, int mode, double x0, double y0,
Expand All @@ -131,6 +132,7 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, double x0, double y0,
"""
validate_resampling(resampling)

cdef StackChecker checker
cdef int i = 0
cdef int retval = 3
cdef int *bandmap = NULL
Expand Down Expand Up @@ -175,6 +177,7 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, double x0, double y0,
cdef GDALDriverH driver = NULL
cdef const char* driver_name = NULL
cdef GDALRasterBandH band = NULL

if CPLGetConfigOption("GDAL_NUM_THREADS", NULL):
interleave = GDALGetMetadataItem(hds, "INTERLEAVE", "IMAGE_STRUCTURE")
if interleave and interleave == b"BAND":
Expand All @@ -187,31 +190,28 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, double x0, double y0,
band = GDALGetRasterBand(hds, bandmap[i])
if band == NULL:
raise ValueError("Null band")
with nogil:
retval = GDALRasterIOEx(
band,
<GDALRWFlag>mode, xoff, yoff, xsize, ysize,
<void *>(<char *>buf + i * bufbandspace),
bufxsize, bufysize, buftype,
bufpixelspace, buflinespace, &extras)

if retval != CE_None:
return exc_wrap_int(retval)

return exc_wrap_int(CE_None)

with stack_errors() as checker:
with nogil:
retval = GDALRasterIOEx(
band,
<GDALRWFlag>mode, xoff, yoff, xsize, ysize,
<void *>(<char *>buf + i * bufbandspace),
bufxsize, bufysize, buftype,
bufpixelspace, buflinespace, &extras)
checker.exc_wrap_int(retval)
return 0
finally:
CPLFree(bandmap)

# Chain errors coming from GDAL.
try:
with nogil:
retval = GDALDatasetRasterIOEx(
hds, <GDALRWFlag>mode, xoff, yoff, xsize, ysize, buf,
bufxsize, bufysize, buftype, count, bandmap,
bufpixelspace, buflinespace, bufbandspace, &extras)

return exc_wrap_int(retval)

with stack_errors() as checker:
with nogil:
retval = GDALDatasetRasterIOEx(
hds, <GDALRWFlag>mode, xoff, yoff, xsize, ysize, buf,
bufxsize, bufysize, buftype, count, bandmap,
bufpixelspace, buflinespace, bufbandspace, &extras)
return checker.exc_wrap_int(retval)
finally:
CPLFree(bandmap)

Expand Down Expand Up @@ -250,6 +250,7 @@ cdef int io_multi_mask(GDALDatasetH hds, int mode, double x0, double y0,
cdef int xsize = <int>max(1, width)
cdef int ysize = <int>max(1, height)

cdef StackChecker checker
cdef GDALRasterIOExtraArg extras
extras.nVersion = 1
extras.eResampleAlg = <GDALRIOResampleAlg>resampling
Expand All @@ -275,15 +276,15 @@ cdef int io_multi_mask(GDALDatasetH hds, int mode, double x0, double y0,
buf = <void *>np.PyArray_DATA(data[i])
if buf == NULL:
raise ValueError("NULL data")
with nogil:
retval = GDALRasterIOEx(
hmask, <GDALRWFlag>mode, xoff, yoff, xsize, ysize, buf, bufxsize,
bufysize, <GDALDataType>1, bufpixelspace, buflinespace, &extras)

if retval:
break
with stack_errors() as checker:
with nogil:
retval = GDALRasterIOEx(
hmask, <GDALRWFlag>mode, xoff, yoff, xsize, ysize, buf, bufxsize,
bufysize, <GDALDataType>1, bufpixelspace, buflinespace, &extras)
retval = checker.exc_wrap_int(retval)

return exc_wrap_int(retval)
return retval


cdef _delete_dataset_if_exists(path):
Expand Down Expand Up @@ -381,6 +382,7 @@ cdef int io_auto(data, GDALRasterBandH band, bint write, int resampling=0) excep
else:
raise ValueError("Specified data must have 2 or 3 dimensions")

# TODO: don't handle, it's inconsistent with the other io_* functions.
except CPLE_BaseError as cplerr:
raise RasterioIOError(str(cplerr))

Expand Down Expand Up @@ -969,7 +971,7 @@ cdef class DatasetReaderBase(DatasetBase):
io_multi_band(self._hds, 0, xoff, yoff, width, height, out, indexes_arr, resampling=resampling)

except CPLE_BaseError as cplerr:
raise RasterioIOError("Read or write failed. {}".format(cplerr))
raise RasterioIOError("Read failed. See previous exception for details.") from cplerr

return out

Expand Down Expand Up @@ -1795,7 +1797,7 @@ cdef class DatasetWriterBase(DatasetReaderBase):
try:
io_multi_band(self._hds, 1, xoff, yoff, width, height, arr, indexes_arr)
except CPLE_BaseError as cplerr:
raise RasterioIOError("Read or write failed. {}".format(cplerr))
raise RasterioIOError("Write failed. See previous exception for details.") from cplerr

def write_band(self, bidx, src, window=None):
"""Write the src array into the `bidx` band.
Expand Down Expand Up @@ -2008,7 +2010,7 @@ cdef class DatasetWriterBase(DatasetReaderBase):
io_band(mask, 1, xoff, yoff, width, height, mask_array)

except CPLE_BaseError as cplerr:
raise RasterioIOError("Read or write failed. {}".format(cplerr))
raise RasterioIOError("Write failed. See previous exception for details.") from cplerr

def build_overviews(self, factors, resampling=Resampling.nearest):
"""Build overviews at one or more decimation factors for all
Expand Down
24 changes: 14 additions & 10 deletions rasterio/_warp.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ from rasterio._base import _transform
from rasterio._base cimport open_dataset
from rasterio._err import (
CPLE_BaseError, CPLE_IllegalArgError, CPLE_NotSupportedError,
CPLE_AppDefinedError, CPLE_OpenFailedError)
CPLE_AppDefinedError, CPLE_OpenFailedError, stack_errors)
from rasterio import dtypes
from rasterio.control import GroundControlPoint
from rasterio.enums import Resampling, MaskFlags, ColorInterp
Expand All @@ -36,7 +36,7 @@ cimport numpy as np
from libc.math cimport HUGE_VAL

from rasterio._base cimport get_driver_name
from rasterio._err cimport exc_wrap, exc_wrap_pointer, exc_wrap_int
from rasterio._err cimport exc_wrap, exc_wrap_pointer, exc_wrap_int, StackChecker
from rasterio._io cimport (
DatasetReaderBase, MemoryDataset, in_dtype_range, io_auto)
from rasterio._features cimport GeomBuilder, OGRGeomBuilder
Expand Down Expand Up @@ -101,6 +101,7 @@ def _transform_geom(
transform = exc_wrap_pointer(OCTNewCoordinateTransformation(src._osr, dst._osr))

factory = new OGRGeometryFactory()

try:
if isinstance(geom, (dict, Mapping, UserDict)) or hasattr(geom, "__geo_interface__"):
out_geom = _transform_single_geom(geom, factory, transform, options, precision)
Expand Down Expand Up @@ -577,6 +578,7 @@ def _reproject(
cdef GDALWarpOperation oWarper
cdef int rows
cdef int cols
cdef StackChecker checker

try:
exc_wrap_int(oWarper.Initialize(psWOptions))
Expand All @@ -589,15 +591,17 @@ def _reproject(
"Chunk and warp window: %d, %d, %d, %d.",
0, 0, cols, rows)

if num_threads > 1:
with nogil:
err = oWarper.ChunkAndWarpMulti(0, 0, cols, rows)
else:
with nogil:
err = oWarper.ChunkAndWarpImage(0, 0, cols, rows)

try:
exc_wrap_int(err)
with stack_errors() as checker:
if num_threads > 1:
with nogil:
err = oWarper.ChunkAndWarpMulti(0, 0, cols, rows)
else:
with nogil:
err = oWarper.ChunkAndWarpImage(0, 0, cols, rows)

err = checker.exc_wrap_int(err)

except CPLE_BaseError as base:
raise WarpOperationError("Chunk and warp failed") from base

Expand Down
5 changes: 2 additions & 3 deletions rasterio/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ def __init__(self, message):
super().__init__('', hint=message)


class RasterioIOError(OSError):
"""Raised when a dataset cannot be opened using one of the
registered format drivers."""
class RasterioIOError(RasterioError, OSError):
"""Raised when a dataset cannot be opened or accessed."""


class NodataShadowWarning(UserWarning):
Expand Down
2 changes: 2 additions & 0 deletions rasterio/gdal.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ cdef extern from "cpl_error.h" nogil:
int CPLGetLastErrorNo()
const char* CPLGetLastErrorMsg()
CPLErr CPLGetLastErrorType()
void *CPLGetErrorHandlerUserData()
void CPLPushErrorHandler(CPLErrorHandler handler)
void CPLPushErrorHandlerEx(CPLErrorHandler handler, void *userdata)
void CPLPopErrorHandler()
void CPLQuietErrorHandler(CPLErr eErrClass, CPLErrorNum nError, const char *pszErrorMsg)

Expand Down
Binary file added tests/data/corrupt.tif
Binary file not shown.
32 changes: 31 additions & 1 deletion tests/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import pytest

import rasterio
from rasterio.errors import DatasetIOShapeError
from rasterio._err import CPLE_AppDefinedError
from rasterio.errors import DatasetIOShapeError, RasterioIOError

# Find out if we've got HDF support (needed below).
try:
Expand Down Expand Up @@ -333,3 +334,32 @@ def test_read_out_mask(path_rgb_byte_tif, out):
with rasterio.open(path_rgb_byte_tif) as src:
with pytest.raises(ValueError):
src.read(indexes=[2], out=out)


def test_chained_io_errors(path_rgb_byte_tif):
"""Get chained exceptions."""
with rasterio.open("tests/data/corrupt.tif") as src:
# RasterioIOError is at the top of the stack (~0).
with pytest.raises(RasterioIOError) as excinfo:
src.read()

assert "Read failed. See previous exception for details." == str(excinfo.value)

# Exception ~1 is a GDAL AppDefinedError mentioning IReadBlock.
exc = excinfo.value.__cause__
assert isinstance(exc, CPLE_AppDefinedError)
msg = str(exc)
assert msg.startswith("tests/data/corrupt.tif")
assert "IReadBlock failed" in msg

# Exception ~2 is another AppDefinedError mentioning TIFFReadEncodedTile.
exc = excinfo.value.__cause__.__cause__
assert isinstance(exc, CPLE_AppDefinedError)
msg = str(exc)
assert "TIFFReadEncodedTile()" in msg

# Exception ~3 is another AppDefinedError mentioning TIFFFillTile.
exc = excinfo.value.__cause__.__cause__.__cause__
assert isinstance(exc, CPLE_AppDefinedError)
msg = str(exc)
assert "TIFFFillTile:Read error" in msg