Skip to content

SAF Create() omits O_RDWR and returns a non-writable file #214

Description

@SoongVilda

I was going through the SAF filesystem implementation and noticed that safFilesystem.Create() currently calls:

return sfs.OpenFile(name, os.O_CREATE|os.O_TRUNC, 0)

Neither os.O_WRONLY nor os.O_RDWR is included. Since os.O_RDONLY is zero, this effectively requests read-only access.

safNode.OpenFile() uses those flags for two separate purposes:

  1. To choose the Android file descriptor mode.
  2. To set canWrite on the returned safFile.

For Android's built-in external storage provider, the missing write flag selects mode "r". O_TRUNC then appends "t", producing "rt". Android's ParcelFileDescriptor.parseMode() accepts only "r", "w", "wt", "wa", "rw", and "rwt", so "rt" is invalid.

For other SAF providers, the underlying descriptor is opened as "rwt", but canWrite is still false because it is calculated only from O_WRONLY and O_RDWR.

Consequently, the built-in provider path fails while opening the descriptor, and the generic provider path returns a safFile whose Write(), WriteAt(), and Truncate() methods return os.ErrInvalid.

This also differs from Syncthing's BasicFilesystem.Create(), which opens the file with O_RDWR|O_CREATE|O_TRUNC.

The smallest fix appears to be:

return sfs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0)

A regression test could call Create() using mock document URIs that exercise both the built-in external storage and generic-provider paths, verify that "rwt" is requested, and write a few bytes through the returned file.

I have not reproduced the failure on-device, but the implementation itself appears deterministically broken. Syncthing does call Filesystem.Create() through osutil.copyFileContents(), although I have not determined how readily that path becomes user-visible for SAF folders.

This is present in BasicSync 3.7 / current master at commit 5b159a7.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions