FSA: Preserve save picker filesystem errors

MERGED2026contentfile_system_accessfix
2026. 9. 5.zbnerd 프로필 이미지zbnerd

showSaveFilePicker()에서 파일 생성이나 truncate가 실패했을 때 원래 파일 시스템 오류를 보존하도록 수정했습니다. 웹사이트가 쓰기 권한 부족이나 부모 디렉터리 부재를 사용자 취소와 구분할 수 있도록 개선했습니다.

문제 설명

저장 파일 선택기는 파일 핸들을 반환하기 전에 선택한 파일을 생성하거나 기존 파일의 길이를 0으로 줄입니다. 기존 구현은 이 작업의 결과를 bool로 전달해 실패 원인을 잃어버렸습니다.

  • 쓰기 권한 부족이나 부모 디렉터리 부재도 일반적인 AbortError로 전달됐습니다.
  • 사용자가 대화상자를 취소한 경우에도 AbortError가 발생하므로, 웹사이트가 취소와 파일 시스템 실패를 구분하기 어려웠습니다.
  • POSIX 로컬 파일 처리 경로와 storage backend 경로 모두에서 오류 정보를 유지해야 했습니다.

해결 내용

  1. POSIX 로컬 파일 헬퍼 CreateAndTruncateLocalFile()의 반환형을 bool에서 base::File::Error로 변경했습니다. open(), fstat(), SetLength() 실패 시 오류를 반환하고, 일반 파일이 아닌 경우에는 FILE_ERROR_NOT_A_FILE을 반환하도록 했습니다.
  2. CreateAndTruncateFile()DidCreateFileToTruncate()의 비동기 콜백도 base::File::Error를 전달하도록 변경해 backend가 반환한 오류를 보존했습니다.
  3. DidCreateAndTruncateSaveFile()에서 일반적인 실패 상태 대신 기존 file_system_access_error::FromFileError() 매핑을 사용하도록 수정했습니다.
if (result != base::File::FILE_OK) {
  std::move(callback).Run(file_system_access_error::FromFileError(result),
                          std::move(result_entries));
  return;
}

쓰기 접근이 거부되면 NoModificationAllowedError, 부모 디렉터리가 없으면 NotFoundError를 전달합니다. 사용자가 선택을 취소하면 기존처럼 AbortError를 전달합니다.

외부 경로의 NativeFileUtilSetLength() 실패를 여전히 FILE_ERROR_FAILED로 반환합니다. 이번 변경은 backend가 제공하는 오류를 보존하며, 해당 backend 내부의 오류 세분화까지 변경하지는 않았습니다.

테스트 방법

다음 회귀 테스트를 추가했습니다.

  • FileSystemAccessManagerImplSaveFileTest를 로컬·외부 경로로 매개변수화해 ReadOnlyFile, MissingParentDirectory, TruncateFailure를 검증합니다. 실패 시 오류 코드와 빈 결과 목록을 확인하고, 기존 파일의 내용이 보존되는지도 확인합니다.
  • Linux·ChromeOS의 TruncateFailure는 Landlock으로 truncate만 제한합니다. 파일 열기와 fstat()이 성공하는지 먼저 확인해 SetLength() 실패 경로를 실제로 거치도록 구성했습니다. 제한을 해제할 수 없는 Landlock의 특성을 고려해 전용 파일 스레드에만 적용하며, 필요한 기능을 지원하지 않는 환경에서는 건너뜁니다.
  • FileSystemChooserBrowserTestSaveFile_CancelDialog, SaveFile_ReadOnlyFile, SaveFile_MissingParentDirectory를 추가해 JavaScript에서 관찰하는 예외 이름을 검증합니다.
  • 읽기 전용 파일 테스트는 실제 쓰기 접근이 차단되는지 먼저 확인합니다. Windows에서는 권한을 복원한 뒤 기존 파일의 내용을 확인하도록 했습니다.

Gerrit에서 Alex Moshchuk과 Rakina Zata Amni의 리뷰를 받았고, Amos Lim의 Commit Queue 제출 후 2026-09-11에 Chromium main에 병합됐습니다. 최종 커밋 위치는 refs/heads/main@{#1696213}입니다.

배운 점

  • 하위 계층의 오류를 bool로 축약하면 상위 API에서 필요한 실패 원인을 복원할 수 없습니다. 비동기 콜백을 포함한 전체 전달 경로에서 오류 타입을 유지해야 합니다.
  • 내부 오류 코드 확인과 JavaScript에서 관찰하는 예외 확인을 함께 구성하면, 구현 계층과 사용자에게 보이는 API 동작을 연결해 검증할 수 있습니다.
  • truncate 실패를 테스트하려면 앞선 파일 열기 단계에서 실패하지 않는 조건을 만들어야 합니다. Landlock으로 특정 연산만 제한하면 목표한 실패 경로를 검증할 수 있습니다.
  • 파일 권한과 backend의 오류 표현은 플랫폼마다 다르므로, 테스트의 전제 조건과 지원 범위를 명시하고 기존 데이터 보존까지 확인해야 합니다.

참고 자료