diff --git a/src/main/java/software/amazon/nio/spi/s3/S3FileSystemProvider.java b/src/main/java/software/amazon/nio/spi/s3/S3FileSystemProvider.java index 89cae7b6..092201b7 100644 --- a/src/main/java/software/amazon/nio/spi/s3/S3FileSystemProvider.java +++ b/src/main/java/software/amazon/nio/spi/s3/S3FileSystemProvider.java @@ -9,7 +9,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.core.async.AsyncRequestBody; -import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.CommonPrefix; @@ -58,6 +57,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static software.amazon.awssdk.http.HttpStatusCode.FORBIDDEN; import static software.amazon.awssdk.http.HttpStatusCode.NOT_FOUND; +import static software.amazon.awssdk.http.HttpStatusCode.OK; import static software.amazon.nio.spi.s3.Constants.PATH_SEPARATOR; import static software.amazon.nio.spi.s3.util.TimeOutUtils.TIMEOUT_TIME_LENGTH_1; import static software.amazon.nio.spi.s3.util.TimeOutUtils.logAndGenerateExceptionOnTimeOut; @@ -537,39 +537,63 @@ public FileStore getFileStore(Path path) { */ @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { + // warn if AccessModes includes WRITE or EXECUTE + for (AccessMode mode : modes) { + if (mode == AccessMode.WRITE || mode == AccessMode.EXECUTE) { + logger.warn("checkAccess: AccessMode '{}' is currently not checked by S3FileSystemProvider", mode); + } + } + + final S3Path s3Path = checkPath(path.toRealPath(NOFOLLOW_LINKS)); + final CompletableFuture response = getCompletableFutureForHead(s3Path); + + long timeOut = TimeOutUtils.TIMEOUT_TIME_LENGTH_1; + TimeUnit unit = MINUTES; + try { - final S3Path s3Path = checkPath(path.toRealPath(NOFOLLOW_LINKS)); - final CompletableFuture response = getCompletableFutureForHead(s3Path); + IOException ioException = (IOException) response.handleAsync((resp, ex) -> { + if (resp.sdkHttpResponse().statusCode() == NOT_FOUND) + return new NoSuchFileException(s3Path.toString()); - long timeOut = TimeOutUtils.TIMEOUT_TIME_LENGTH_1; - TimeUnit unit = MINUTES; + if (resp.sdkHttpResponse().statusCode() == FORBIDDEN) + return new AccessDeniedException(s3Path.toString()); - try { - SdkHttpResponse httpResponse = response.get(timeOut, unit).sdkHttpResponse(); - if (httpResponse.isSuccessful()) return; + if (resp.sdkHttpResponse().statusCode() != OK) + return new IOException(String.format("exception occurred while checking access, response code was '%d'", + resp.sdkHttpResponse().statusCode())); - // - // TODO: it looks like if status is not SUCCESS an ExecutionException - // is thrown, therefore the following error handling is never - // triggered - // + if( ex != null){ + return ex; + } - if (httpResponse.statusCode() == FORBIDDEN) - throw new AccessDeniedException(s3Path.toString()); + // possible success but ListObjectsV2Responses can be empty so need to check that. + if (resp instanceof ListObjectsV2Response) { + ListObjectsV2Response listResp = (ListObjectsV2Response) resp; - if (httpResponse.statusCode() == NOT_FOUND) - throw new NoSuchFileException(s3Path.toString()); + if (listResp.hasCommonPrefixes() && !listResp.commonPrefixes().isEmpty()) { + logger.debug("checkAccess: access is OK"); + return null; + } - throw new IOException(String.format("exception occurred while checking access, response code was '%d'", - httpResponse.statusCode())); + if (listResp.hasContents() && !listResp.contents().isEmpty()){ + logger.debug("checkAccess: access is OK"); + return null; + } - } catch (TimeoutException e) { - throw logAndGenerateExceptionOnTimeOut(logger, "checkAccess", timeOut, unit); + return new NoSuchFileException(s3Path.toString()); + } + + logger.debug("checkAccess: access is OK"); + return null; + }).get(timeOut, unit); + + // if handling the response produced an exception we throw it, access is not OK. + if (ioException != null) { + throw ioException; } - } catch (ExecutionException e) { - throw new IOException(e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + throw logAndGenerateExceptionOnTimeOut(logger, "checkAccess", timeOut, unit); + } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } @@ -582,6 +606,8 @@ private CompletableFuture getCompletableFutureForHead(S3Pa final CompletableFuture response; if (s3Path.equals(s3Path.getRoot())) { response = s3Client.headBucket(request -> request.bucket(bucketName)); + } else if (s3Path.isDirectory()) { + response = s3Client.listObjectsV2(req -> req.bucket(bucketName).prefix(s3Path.getKey())); } else { response = s3Client.headObject(req -> req.bucket(bucketName).key(s3Path.getKey())); } diff --git a/src/main/java/software/amazon/nio/spi/s3/S3Path.java b/src/main/java/software/amazon/nio/spi/s3/S3Path.java index 5cc3bd29..abc8540b 100644 --- a/src/main/java/software/amazon/nio/spi/s3/S3Path.java +++ b/src/main/java/software/amazon/nio/spi/s3/S3Path.java @@ -5,6 +5,9 @@ package software.amazon.nio.spi.s3; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.nio.spi.s3.config.S3NioSpiConfiguration; + import java.io.File; import java.io.IOError; import java.net.URI; @@ -27,9 +30,6 @@ import static software.amazon.nio.spi.s3.Constants.PATH_SEPARATOR; import static software.amazon.nio.spi.s3.S3FileSystemProvider.checkPath; -import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.nio.spi.s3.config.S3NioSpiConfiguration; - @SuppressWarnings("NullableProblems") class S3Path implements Path {