From a29605750d23646c5cc91bba3b3df9204fee1eba Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 7 Nov 2022 21:33:17 +0100 Subject: [PATCH] lib/fs: Try to remove read only Windows files (fixes #3744) (#8650) This happens when folders contain a custom icon. Co-authored-by: Alexandre Alves --- lib/fs/basicfs.go | 8 -------- lib/fs/basicfs_unix.go | 8 ++++++++ lib/fs/basicfs_windows.go | 15 +++++++++++++++ lib/fs/basicfs_windows_test.go | 23 +++++++++++++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/lib/fs/basicfs.go b/lib/fs/basicfs.go index 5d5f35a40..8ae132592 100644 --- a/lib/fs/basicfs.go +++ b/lib/fs/basicfs.go @@ -178,14 +178,6 @@ func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) { return basicFileInfo{fi}, err } -func (f *BasicFilesystem) Remove(name string) error { - name, err := f.rooted(name) - if err != nil { - return err - } - return os.Remove(name) -} - func (f *BasicFilesystem) RemoveAll(name string) error { name, err := f.rooted(name) if err != nil { diff --git a/lib/fs/basicfs_unix.go b/lib/fs/basicfs_unix.go index 77e8ff0c0..e1b3967eb 100644 --- a/lib/fs/basicfs_unix.go +++ b/lib/fs/basicfs_unix.go @@ -74,6 +74,14 @@ func (f *BasicFilesystem) Lchown(name, uid, gid string) error { return os.Lchown(name, nuid, ngid) } +func (f *BasicFilesystem) Remove(name string) error { + name, err := f.rooted(name) + if err != nil { + return err + } + return os.Remove(name) +} + // unrootedChecked returns the path relative to the folder root (same as // unrooted) or an error if the given path is not a subpath and handles the // special case when the given path is the folder root without a trailing diff --git a/lib/fs/basicfs_windows.go b/lib/fs/basicfs_windows.go index 83445285a..60fdfb764 100644 --- a/lib/fs/basicfs_windows.go +++ b/lib/fs/basicfs_windows.go @@ -190,6 +190,21 @@ func (f *BasicFilesystem) Lchown(name, uid, gid string) error { return windows.SetSecurityInfo(hdl, windows.SE_FILE_OBJECT, si, (*windows.SID)(ownerSID), (*windows.SID)(groupSID), nil, nil) } +func (f *BasicFilesystem) Remove(name string) error { + name, err := f.rooted(name) + if err != nil { + return err + } + err = os.Remove(name) + if os.IsPermission(err) { + // Try to remove the read-only attribute and try again + if os.Chmod(name, 0600) == nil { + err = os.Remove(name) + } + } + return err +} + // unrootedChecked returns the path relative to the folder root (same as // unrooted) or an error if the given path is not a subpath and handles the // special case when the given path is the folder root without a trailing diff --git a/lib/fs/basicfs_windows_test.go b/lib/fs/basicfs_windows_test.go index 48445d7ba..5526dd220 100644 --- a/lib/fs/basicfs_windows_test.go +++ b/lib/fs/basicfs_windows_test.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "strings" + "syscall" "testing" ) @@ -192,3 +193,25 @@ func TestGetFinalPath(t *testing.T) { } } } + +func TestRemoveWindowsDirIcon(t *testing.T) { + //Try to delete a folder with a custom icon with os.Remove (simulated by the readonly file attribute) + + fs, dir := setup(t) + relativePath := "folder_with_icon" + path := filepath.Join(dir, relativePath) + + if err := os.Mkdir(path, os.ModeDir); err != nil { + t.Fatal(err) + } + ptr, err := syscall.UTF16PtrFromString(path) + if err != nil { + t.Fatal(err) + } + if err := syscall.SetFileAttributes(ptr, uint32(syscall.FILE_ATTRIBUTE_DIRECTORY+syscall.FILE_ATTRIBUTE_READONLY)); err != nil { + t.Fatal(err) + } + if err := fs.Remove(relativePath); err != nil { + t.Fatal(err) + } +}