Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a temporary file for writes #9236

Merged
merged 13 commits into from
Mar 31, 2024
Merged

Conversation

kirawi
Copy link
Member

@kirawi kirawi commented Jan 4, 2024

Supersedes #4276 and resolves #3967

I initially suspected it had to do with not seeking to the beginning of the file, but it turned out to be something equally as dumb:

  • rename only changes the path of the open inode to the new path, so any open handles to the old inode are not affected. Thus, I had to reopen the temporary file after each write.
  • rename can replace readonly files if the directory has write permissions. So failing if the file at the path is readonly remains the last task for the tests to pass. Actually, it would be good to also apply the same permissions and everything to the new file as well.

Bear in mind I was unaware of even what an inode was until 5 minutes ago, so I might be wrong.

@kirawi kirawi marked this pull request as ready for review January 5, 2024 00:25
@kirawi kirawi force-pushed the move branch 2 times, most recently from 92a5907 to ea7899d Compare January 5, 2024 00:27
@pascalkuthe
Copy link
Member

I.plementing this correctly is going to be a lot harder. If you create a new temporary field all file metadata (timestamps, permissions, owner, groups,..) need to be copied to the new file. That code is highly platform dependent (not supported by std) and not easy to implement

@kirawi
Copy link
Member Author

kirawi commented Jan 5, 2024

Gotcha, I'll look into it. I think that the only thing that is platform-dependent is owners/groups and permissions. Unix seems simple, I don't know about Windows though (Vim doesn't try for Windows with its undofiles).

@kirawi kirawi marked this pull request as draft January 5, 2024 01:18
@kirawi
Copy link
Member Author

kirawi commented Jan 5, 2024

I'll be punting this to the backburner for now. I'd have to work with winapi for the aforementioned metadata which would require me to learn unsafe Rust.

@kirawi
Copy link
Member Author

kirawi commented Jan 7, 2024

How would you feel about restricting this to only Unix systems? I wrote code to copy permissions on Windows:

fn chown(from: &Path, to: &Path) -> std::io::Result<()> {
    use std::os::windows::io::AsRawHandle;

    // CHECK: Requires read
    let handle = HANDLE(std::fs::File::open(from)?.as_raw_handle() as isize);
    let mut owner = PSID::default();
    let mut group = PSID::default();
    unsafe {
        windows::Win32::Security::Authorization::GetSecurityInfo(
            handle,
            SE_FILE_OBJECT,
            OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
            Some(&mut owner),
            Some(&mut group),
            None,
            None,
            None,
        )?;
    }

    // CHECK: requires write
    let handle = HANDLE(std::fs::File::open(to)?.as_raw_handle() as isize);
    unsafe {
        windows::Win32::Security::Authorization::SetSecurityInfo(
            handle,
            SE_FILE_OBJECT,
            OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
            owner,
            group,
            None,
            None,
        )?;
    }

    Ok(())
}

But aside from the questionable safety of what I've written, I'd rather not have to maintain it since this kind of stuff is sparsely documented in the API.

@pascalkuthe
Copy link
Member

pascalkuthe commented Jan 7, 2024

I took a look at what vim does... It's not easy tgrwap but what I found is that:

@kirawi kirawi marked this pull request as ready for review January 8, 2024 23:11
@kirawi kirawi force-pushed the move branch 2 times, most recently from c3fc081 to cff812f Compare January 8, 2024 23:55
helix-view/src/faccess.rs Outdated Show resolved Hide resolved
@kirawi kirawi marked this pull request as draft January 9, 2024 01:33
@pascalkuthe
Copy link
Member

the integration tests fail for me locally toso its not just a permission issue in the runner

@pascalkuthe
Copy link
Member

pascalkuthe commented Jan 9, 2024

I am not sure if you are quite using tmp file correctly. The following two caveats seem extremely relevant:

    /// Note: Temporary files cannot be persisted across filesystems. Also
    /// neither the file contents nor the containing directory are
    /// synchronized, so the update may not yet have reached the disk when
    /// `persist` returns.
    ///
    /// # Security
    ///
    /// Only use this method if you're positive that a temporary file cleaner
    /// won't have deleted your file. Otherwise, you might end up persisting an
    /// attacker controlled file.

I am pretty sure mky tmpfiles are in a separate btrfs partition so that's why its not working

@pascalkuthe
Copy link
Member

I think you will want to use https://docs.rs/tempfile/latest/tempfile/struct.Builder.html#method.tempfile_in to create the tomprary file in the same dirtory as the destination file. To avoid creating a bunch of random files you can set the prefix to the filename (including extension) and the suffix to bak so its clear we are writing temporary backup files

@pascalkuthe
Copy link
Member

I don't think the chown stuff ist actually the problem. We ignore errors so I don't see how that could block writing the file

@pascalkuthe
Copy link
Member

and two test failures are simple bugs in the testing infrastructure keeping the file handle open and hence reading from the closed file

@pascalkuthe
Copy link
Member

pascalkuthe commented Jan 9, 2024

ok I did those changes myself (hope that's ok its separate commits so nothing was lost) locally integration tests pass let's see if CI is green. I think this should handle the subtleties for the most part

@kirawi
Copy link
Member Author

kirawi commented Jan 9, 2024

I can test on my Windows machine since I can replicate it in integration tests, if you'd like? I'm also trying to debug it on my end.

@kirawi
Copy link
Member Author

kirawi commented Jan 9, 2024

sync_all on Windows calls FlushFileBuffers, which shouldn't fail if the handle has write permissions. However, it does note that: To flush all open files on a volume, call FlushFileBuffers with a handle to the volume. The caller must have administrative privileges. For more information, see [Running with Special Privileges](https://learn.microsoft.com/en-us/windows/desktop/SecBP/running-with-special-privileges).

@pascalkuthe
Copy link
Member

pascalkuthe commented Jan 9, 2024

yeah I had to stop working due to a meeting. I changed the code to move the old file to a backup and then write to the intended path (backup file is only deleted on successful write). This is how vim handles this and should remove the permission concerns from copy_metadata (since we won't need to move the file afterwards it doesn't matter if we don't have permissions to access it)

I believe that the code is correct now and the integration tests just fail because of some tests bugs. Potentially the sync_all call. It does seem to be panicking there. Do we really need to call that?

@pascalkuthe
Copy link
Member

ah looks like you fixed it already, great work! I undid my debug changes which made the CI fail and I noticed that we should be flusing/syncing the old file should not actually be needed since we are replacing the file (so a flush on the old file won't do much good).

Lets' see if this passes CI it works locally at least (on linux but seeing that you fixed windows CI I doubt that this would regress that)

@pascalkuthe
Copy link
Member

if CI passes we just need to log (with log::error) all the IO error we are currently ignoring then I think this should be good to go

@kirawi
Copy link
Member Author

kirawi commented Jan 9, 2024

Before this is ready to merge, I want to port the faccess code for Windows to windows-sys and make the logic match since I used ? where it shouldn't be used according to the original logic.

@pascalkuthe
Copy link
Member

yeah in ead of bailing out on error we should just log all of them with log::errors. Using windows-sys would be great 👍 Thanks for working on the low level stuff that is pretty gnarly.

One thing vim does that we don't is that w! will turn a readonly file into a writable one if possible. We should do that too by ignoring the readonly check and removing readonly form the permissions in that case

@pascalkuthe
Copy link
Member

its not critical to handle the readonly stuff in this PR since we didn't do anything about that currently either so if that turns out to be more complicated then just leave it for future work

pascalkuthe
pascalkuthe previously approved these changes Mar 31, 2024
Copy link
Member

@pascalkuthe pascalkuthe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(rebased as there were just some minor conflicts in the integration tests)

Copy link
Member

@pascalkuthe pascalkuthe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ups)

Copy link
Member

@the-mikedavis the-mikedavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @kirawi & @pascalkuthe !

@the-mikedavis the-mikedavis merged commit 88d455a into helix-editor:master Mar 31, 2024
6 checks passed
@kirawi kirawi deleted the move branch March 31, 2024 23:46
Desdaemon pushed a commit to Desdaemon/helix that referenced this pull request Apr 3, 2024
postsolar pushed a commit to postsolar/helix that referenced this pull request Apr 4, 2024
postsolar pushed a commit to postsolar/helix that referenced this pull request Apr 4, 2024
postsolar pushed a commit to postsolar/helix that referenced this pull request Apr 4, 2024
kirawi added a commit to kirawi/helix that referenced this pull request Apr 9, 2024
postsolar pushed a commit to postsolar/helix that referenced this pull request Apr 20, 2024
mtoohey31 pushed a commit to mtoohey31/helix that referenced this pull request Jun 2, 2024
Vulpesx pushed a commit to Vulpesx/helix that referenced this pull request Jun 7, 2024
smortime pushed a commit to smortime/helix that referenced this pull request Jul 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-helix-term Area: Helix term improvements C-enhancement Category: Improvements S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

data loss possible on forced kill
3 participants