Skip to content

Conversation

@Neko-Box-Coder
Copy link
Contributor

Switching between git branches on Windows sometimes fails due to opened files in micro.
Adding missing file closes fixes this.

size, err := file.Write(b)
if err != nil {
err = util.OverwriteError{err, backupName}
file.Close()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good catch. But why just here? What about all other error returns above, where we also leak file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, somehow missed it :/

Anyway, I moved the file opening closer to when we need it, instead of the beginning so now we always do a backup first, which I think is good.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm guilty for not adding the file.Close() in the the err-paths above.
Having this backup could be unintentionally, because the user might not like having them at all (permbackup set to false). When the open fails right after and the user decides to drop that change anyway, then the user is faced with the error prompt next time opening this file.

Do we really want that?

Copy link
Contributor Author

@Neko-Box-Coder Neko-Box-Coder Jul 19, 2025

Choose a reason for hiding this comment

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

then the user is faced with the error prompt next time opening this file.

I don't have enough context, what do you mean exactly? Do we check for backup files (and warn the user?) when we open the same file again?

Copy link
Collaborator

Choose a reason for hiding this comment

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

const BackupMsg = `A backup was detected for:
%s
This likely means that micro crashed while editing this file,
or another instance of micro is currently editing this file,
or an error occurred while saving this file so it may be corrupted.
The backup was created on %s and its path is:
%s
* 'recover' will apply the backup as unsaved changes to the current buffer.
When the buffer is closed, the backup will be removed.
* 'ignore' will ignore the backup, discarding its changes. The backup file
will be removed.
* 'abort' will abort the open operation, and instead open an empty buffer.
Options: [r]ecover, [i]gnore, [a]bort: `

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see.

I have moved the backup part out so I don't have to put file close in so many places.
It now does roughly the same as before except it retains the backup if the file close failed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we really want that?

We don't. As you might remember from #3273, the code structure is what it is, among other things, precisely because we wanted to avoid that.

@Neko-Box-Coder Neko-Box-Coder force-pushed the FileClosing branch 5 times, most recently from 2672aa8 to c28c67e Compare July 19, 2025 17:44
return err
}

func (b *Buffer) backupFile(path string) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

writeBackup()? backupFile() is not the most suitable name here (for example, because if backupFile() backs up a file, one would expect that backupDir() backs up a directory), right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, we are backing up a file, maybe we should rename backupDir() to getBackupDir() (or backupDirPath()) instead?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not the only function where we are backing up a file. We are also doing it in Backup() (because we create backups in more cases than when the user saves a file).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So we should have a common function that performs the backup?

Copy link
Collaborator

@dmaluka dmaluka Jul 19, 2025

Choose a reason for hiding this comment

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

No, because they are different kinds of backups, performed in different ways, as you can see from the code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm... it looks like we have a couple of bugs with saving with sudo:

🤦‍♂️

Looks like we've to store some more stuff for the withSudo-case and start the cmd in func (wf wrappedFile) Write() earliest.
Good hint regarding conv=fsync, this should do the trick.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we consider this as part of milestone v2.0.15?
From my perspective: yes

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like we've to store some more stuff for the withSudo-case and start the cmd in func (wf wrappedFile) Write() earliest.

Which would mean that if sudo dd ... fails just because the user hasn't typed the correct password (so dd was not actually executed, so the file was not corrupted), we still write the backup, and don't remove it?

Should we consider this as part of milestone v2.0.15?

You mean the sudo issues? Those are not regressions (before #3273, save with sudo was not safe either, since any save was unsafe), so not necessarily?

Although in general, providing safe writes for sudo indeed seems no less important than providing safe writes in normal case (perhaps even more important, given that with sudo we have absolute power, and are able to corrupt any file in the filesystem).

Copy link
Collaborator

@JoeKar JoeKar Jul 22, 2025

Choose a reason for hiding this comment

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

Which would mean that if sudo dd ... fails just because the user hasn't typed the correct password (so dd was not actually executed, so the file was not corrupted), we still write the backup, and don't remove it?

No, it would require more logic, but anyway it is much easier and conv=notrunc,fsync (fsync only just to be sure) is all we need.

Although in general, providing safe writes for sudo indeed seems no less important than providing safe writes in normal case (perhaps even more important, given that with sudo we have absolute power, and are able to corrupt any file in the filesystem).

This is my concern too.
We should fix this, since the fix looks small and easy (-> #3814).

Copy link
Collaborator

Choose a reason for hiding this comment

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

but anyway it is much easier

No it is not (unless you have an idea of something as easy as #3814 but actually working).

if err != nil {
err = util.OverwriteError{err, backupName}
return size, err
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this just to avoid having err2? Why avoid that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

err2 replaces err anyway in the original.

Here, we are returning immediately if the write or close failed, for clarity.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was asking why do you need this extra scope. I assumed you added it in order to be able to shadow the err variable at line 395. If so, why need to shadow it instead of keeping using another variable? If not, why need this extra scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The scope is just for clarity together with the comment.
If we failed inside this scope, we keep the backup (if any).

The err at 395 should be normal assignment instead, missed it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Now I'm not sure this scope actually improves readability. You had to add this size := 0 which serves no purpose but to work around assigning size inside this scope...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a minor thing anyway, that extra size := 0 makes no difference.

The block is surrounded by forceKeepBackup and the comment applies pretty nicely to the whole block as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, no biggie.

if err != nil {
file.Close()
err = util.OverwriteError{err, backupName}
return 0, err
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can just return 0, util.OverwriteError{err, backupName}?

return 0, err
}
}
b.forceKeepBackup = true
Copy link
Collaborator

@dmaluka dmaluka Jul 19, 2025

Choose a reason for hiding this comment

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

Why move setting forceKeepBackup here? We force keeping the backup in case we actually have successfully created this backup, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the original, we force backup (or rather retain the backup) if we failed to do backup or failed to write to the file so that we keep trying again later.

We are doing the same here except we also force the backup if we failed to close the file as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the original, we force backup (or rather retain the backup) if we failed to do backup

No we don't. In the original we don't set forceKeepBackup to true until we have successfully written the backup and are ready to start writing the original file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No we dont.

True, missed that. But if we failed to backup, then we should keep whatever backup was there no?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why would we? If we failed to backup, the backup file is a garbage, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I missed it but we are already removing it inside backupFile(). Should be fine then right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

And then safeWrite() returns with an error and leaves forceKeepBackup set to true, which is not what we want, right?

@Neko-Box-Coder Neko-Box-Coder force-pushed the FileClosing branch 2 times, most recently from becf2a3 to ec01071 Compare July 19, 2025 19:33
@dmaluka dmaluka merged commit d7e43d4 into zyedidia:master Jul 22, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants