Could not set write directory on Android 6

Issue #132 resolved
Sergej Nisin created an issue

Saving files on Android 6 does not work anymore. It did work on Android 5.x.
Tested on Nexus 5 stock and OnePlus One with CM13.

The folowing code:

function love.load()
    love.filesystem.setIdentity( "loveTestSave" )

    print("Files: ")
    local files = love.filesystem.getDirectoryItems( "" )
    for i, v in ipairs(files) do
        print(i..". "..v)
    end

    local success, err = love.filesystem.write( "asd.fg", "hello" )
    print("asd.fg writing: "..tostring(success).." - "..tostring(err))

    print("Files: ")
    local files = love.filesystem.getDirectoryItems( "" )
    for i, v in ipairs(files) do
        print(i..". "..v)
    end
end

outputs

SDL/APP : [LOVE] Files:
SDL/APP : [LOVE] 1. conf.lua
SDL/APP : [LOVE] 2. main.lua
SDL/APP : [LOVE] asd.fg writing: nil - Could not set write directory.
SDL/APP : [LOVE] Files:
SDL/APP : [LOVE] 1. conf.lua
SDL/APP : [LOVE] 2. main.lua

Logcat output:

07-09 23:33:03.935   776   966 I ActivityManager: START u0 {act=android.intent.action.VIEW dat=file:///storage/emulated/0/game.love typ=*/* flg=0x10000001 cmp=org.love2d.android/.GameActivity (has extras)} from uid 10137 on display 0
07-09 23:33:03.941   204   809 D audio_hw_primary: select_devices: out_snd_device(2: speaker) in_snd_device(0: none)
07-09 23:33:03.941   204   809 D msm8974_platform: platform_send_audio_calibration: sending audio calibration for snd_device(2) acdb_id(15)
07-09 23:33:03.942   204   809 D audio_hw_primary: enable_snd_device: snd_device(2: speaker)
07-09 23:33:03.944   204   809 D audio_hw_primary: enable_audio_route: apply and update mixer path: low-latency-playback
07-09 23:33:04.000   776  4632 I ActivityManager: Start proc 14334:org.love2d.android/u0a126 for activity org.love2d.android/.GameActivity
07-09 23:33:04.049   776  2077 I ActivityManager: Config changes=480 {1.0 262mcc7mnc de_DE ldltr sw360dp w592dp h336dp 480dpi nrml land finger -keyb/v/h -nav/h s.104}
07-09 23:33:04.051   776   875 I InputReader: Reconfiguring input devices.  changes=0x00000004
07-09 23:33:04.051   776   875 I InputReader: Device reconfigured: id=4, name='touch_dev', size 1080x1920, orientation 1, mode 1, display id 0
07-09 23:33:04.123 13499 13558 D OpenGLRenderer: endAllStagingAnimators on 0xa1075c80 (SEGridView) with handle 0xb3f60740
07-09 23:33:04.133 14334 14334 D GameActivity: started
07-09 23:33:04.136 14334 14334 D GameActivity: Received intent with path: /storage/emulated/0/game.love
07-09 23:33:04.136 14334 14334 D GameActivity: new gamePath: /storage/emulated/0/game.love
07-09 23:33:04.136 14334 14334 V SDL     : Device: hammerhead
07-09 23:33:04.136 14334 14334 V SDL     : Model: Nexus 5
07-09 23:33:04.136 14334 14334 V SDL     : onCreate(): null
07-09 23:33:04.136 14334 14334 V SDL     : startNative()
07-09 23:33:04.205 14334 14334 V SDL     : Got filename: /storage/emulated/0/game.love
07-09 23:33:04.206 14334 14334 V SDL     : onResume()
07-09 23:33:04.232 14334 14334 V SDL     : surfaceCreated()
07-09 23:33:04.232 14334 14334 V SDL     : surfaceChanged()
07-09 23:33:04.232 14334 14334 V SDL     : pixel format RGB_565
07-09 23:33:04.232 14334 14334 V SDL     : Window size: 1776x1080
07-09 23:33:04.238 14334 14362 I SDL     : SDL_Android_Init()
07-09 23:33:04.238 14334 14362 I SDL     : SDL_Android_Init() finished!
07-09 23:33:04.241 14334 14362 D GameActivity: called getGamePath(), game path = /storage/emulated/0/game.love
07-09 23:33:04.244 14334 14362 I SDL/APP : Error: Could not create directory /storage/emulated/0/Android/data/org.love2d.android/files/save/archive
07-09 23:33:04.244 14334 14362 I SDL/APP : Error: Could not create save directory /storage/emulated/0/Android/data/org.love2d.android/files/save/archive!
07-09 23:33:04.255 14334 14362 W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
07-09 23:33:04.264   204   809 D audio_hw_primary: out_set_parameters: enter: usecase(1: low-latency-playback) kvpairs: routing=2
07-09 23:33:04.282 14334 14362 I Adreno-EGL: <qeglDrvAPI_eglInitialize:379>: QUALCOMM Build: 10/21/15, 369a2ea, I96aee987eb
07-09 23:33:04.298 14334 14334 V SDL     : onWindowFocusChanged(): true
07-09 23:33:04.302   776   795 V WindowManager: Layouts looping: On entry to LockedInner, mPendingLayoutChanges = 0x1
07-09 23:33:04.303   776   795 V WindowManager: Layouts looping: after finishPostLayoutPolicyLw, mPendingLayoutChanges = 0x0
07-09 23:33:04.303   776   795 V WindowManager: Layouts looping: mLayoutNeeded, mPendingLayoutChanges = 0x1
07-09 23:33:04.304   776   795 V WindowManager: Layouts looping: On entry to LockedInner, mPendingLayoutChanges = 0x1
07-09 23:33:04.304   776   795 V WindowManager: Layouts looping: after finishPostLayoutPolicyLw, mPendingLayoutChanges = 0x0
07-09 23:33:04.305   776   795 V WindowManager: Layouts looping: mLayoutNeeded, mPendingLayoutChanges = 0x1
07-09 23:33:04.307   776   795 E WindowManager: Performed 6 layouts in a row. Skipping
07-09 23:33:04.312   776   795 I ActivityManager: Displayed org.love2d.android/.GameActivity: +336ms
07-09 23:33:04.342   776   795 I WindowManager: Screen frozen for +316ms due to Window{2604fce u0 Starting org.love2d.android}
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] Files:
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] 1. conf.lua
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] 2. main.lua
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] asd.fg writing: nil - Could not set write directory.
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] Files:
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] 1. conf.lua
07-09 23:33:04.367 14334 14362 I SDL/APP : [LOVE] 2. main.lua

Comments (16)

  1. David Serrano

    You can try using external storage. I can confirm the issue and using external storage seemed to work for me. You can use external storage by setting the flag.

     t.externalstorage = false 
    
  2. Fernando Paredes Garcia

    @David Serrano Guess you mean:

    t.externalstorage = true
    

    Because that is what I used and it works!

    Actually, I'm using it along setIdentity():

      t.identity = "DCApp"
      fs.setIdentity(t.identity)
      t.externalstorage = true
    
  3. David Serrano

    Yup, that is what i meant. I did look into why the internal storage setting is not working and I'm a bit stumped tbh. If you have any idea why it wouldn't be working please let me know. I might be able to fix it if its simple enough.

  4. Fernando Paredes Garcia

    That is not bug, things work by design the right way. From Android 6.0, applications are supposed to store files into a private (kind of chroot) environment, that nobody else should access, this is so strict that one has to explicitly allow external storage, even if there is not one (in such case Android emulates it by creating a folder into main storage).
    This should be documented boldly so we prevent people's frustration.

  5. David Serrano

    Well yes. But it is documented how that flag works. (Or at least I hope so). The issue here is that the internal, private storage is not working. Which it should be because on the Android docs nothing changed between Android versions.

  6. Younghwan Kim

    I traced this problem (sorry for my bad English).

    love.filesystem.write() is called, and failed at this point...

    // in 'modules\filesystem\physfs\Filesystem.cpp'
    (.... callstack omitted)
    -> Filesystem::setupWriteDirectory()
    -> Filesystem::createDirectory(const char *dir)
    -> PHYSFS_mkdir() // <==FAILED HERE, PHYSFS_getLastError() print just 'not found'
    

    So, I printed log for some variables related path...

    Android 4.x (WORKING)
    
    I/SDL/APP : getUserDirectory() : /data/
    I/SDL/APP : temp_writedir : /data/
    I/SDL/APP : temp_createdir : data/com.package.name/files/save/myIdentity
    
    Android 6.0 (NOT WORKING)
    
    I SDL/APP : getUserDirectory() : /data/
    I SDL/APP : temp_writedir : /data/
    I SDL/APP : temp_createdir : user/0/com.package.name/files/save/myIdentity
    

    As you know, from Android 6 internal storage path is changed like '/data/user/0/...' for multi-user support.
    And our PHYSFS_mkdir() function's reference is...

    ...
    So if you've got the write dir set to "C:\mygame\writedir" and call
    PHYSFS_mkdir("downloads/maps") then the directories
    "C:\mygame\writedir\downloads" and "C:\mygame\writedir\downloads\maps"
    will be created if possible.
    ...
    

    It seems like PHYSFS_mkdir() failed while it try access path like '/data/' or '/data/user/' because the path is not access-permitted for applications..(?)

    Now I solve this problem with modify Filesystem::getUserDirectory()
    from

    std::string Filesystem::getUserDirectory()
    {
    #ifdef LOVE_IOS
        // PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
        static std::string userDir = normalize(love::ios::getHomeDirectory());
    #else
        static std::string userDir = normalize(PHYSFS_getUserDir());
    #endif
    
        return userDir;
    }
    

    to

    std::string Filesystem::getUserDirectory()
    {
    #ifdef LOVE_IOS
        // PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
        static std::string userDir = normalize(love::ios::getHomeDirectory());
    #elif LOVE_ANDROID
        static std::string userDir;
        if (isAndroidSaveExternal())
            userDir = SDL_AndroidGetExternalStoragePath();
        else
            userDir = SDL_AndroidGetInternalStoragePath();
    #else
        static std::string userDir = normalize(PHYSFS_getUserDir());
    #endif
    
        return userDir;
    }
    

    After this modification, the log is like this.. on Android 6.0

    I SDL/APP : getUserDirectory() : /data/user/0/com.package.name/files
    I SDL/APP : temp_writedir : /data/user/0/com.package.name/files
    I SDL/APP : temp_createdir : save/myIdentity
    

    and it solve the problem, but I'm not convinced that this modification is appropriate or not.
    (sorry again for my bad english, but hope this helps)

  7. Laurent Zubiaur

    Any news on this issue? I still face the same problem with Android 6. I confirm that the hack proposed by Kim works fine and I'm able to write to the internal storage.

  8. Henrique Gemignani

    My workaround for this issue is to edit SDL_AndroidGetInternalStoragePath in SDL_android.c. Instead of calling getFilesDir().getAbsolutePath(), I changed that to a getFilesDir().getCanonicalPath().

    What happens is that getFilesDir().getAbsolutePath() is the path of a symlink to /data/data/{gameBundleId}/files, not that path directly. For some reason, when PhysFS tries to use that as a base path it ends triggering the selinux security features, that are enforced by default starting in Marshmallow.

    Ideally this should be submitted as a patch to SDL, but I haven't got around to that and my code is technically incorrect. (getCanonicalPath may throw some Exceptions and I don't handle then, but I've never seen these occur.)

  9. Gustavo Alberto Lara Gómez

    OK but, that's and old version of the apk and the commit you posted above is from 2018, does it really have the fix in it?

    EDIT: I see, I'll check that out and tell you.

  10. Gustavo Alberto Lara Gómez

    I just noticed I posted the full log and not the specific LOVE output, I'll post it later.

  11. Log in to comment