Skip to content

Fix nob_needs_rebuild with open files in windows - #246

Open
eduardomosko wants to merge 1 commit into
tsoding:mainfrom
eduardomosko:main
Open

Fix nob_needs_rebuild with open files in windows#246
eduardomosko wants to merge 1 commit into
tsoding:mainfrom
eduardomosko:main

Conversation

@eduardomosko

Copy link
Copy Markdown

Windows sucks

But anyway, I need to check if an exe needs_rebuild while it is running because of hot-reload reasons, and it won't work with GENERIC_READ.

As per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea changing that to 0 works for querying the modification time.

If this parameter is zero, the application can query certain metadata such as file, directory, or device attributes without accessing that file or device, even if GENERIC_READ access would have been denied.

So yeah, accept this if you want.

@rexim

rexim commented Jul 15, 2026

Copy link
Copy Markdown
Member

Could you also please provide a minimal example that reproduces the problem you are having.

@eduardomosko

Copy link
Copy Markdown
Author

Yes, for sure. As I said, this has been an issue when hot-reloading, so to mimic that I just open the file for reading.

#define NOB_IMPLEMENTATION
#include "nob.h"

int main(int argc, char **argv) {
    NOB_GO_REBUILD_URSELF(argc, argv);

	const char *this_file = __FILE__;
	const char *file = "output.txt";
	const char *contents = "This is an example file\n";

	if (nob_file_exists(file)) {
		// Mimics running an exe
		FILE *f = fopen(file, "r");
	}

	if (nob_needs_rebuild1(file, this_file)) {
		nob_log(NOB_INFO, "rebuilding...");
		nob_write_entire_file(file, contents, strlen(contents));
	} else {
		nob_log(NOB_INFO, "no rebuild necessary");
	}
}

This works fine in linux:

$ clang-21 -o nob nob.c 
$ ./nob 
[INFO] rebuilding...
$ ./nob 
[INFO] no rebuild necessary

But in windows it errors and always tries to rebuild:

PS > clang -o nob.exe nob.c
PS > ./nob.exe
[INFO] rebuilding...
PS > ./nob.exe
[ERROR] Could not open file output.txt: The process cannot access the file because it is being used by another process.
[INFO] rebuilding...

With my change, the output is consistent in both platforms:

PS > clang -o nob.exe nob.c
PS > ./nob.exe
[INFO] rebuilding...
PS > ./nob.exe
[INFO] no rebuild necessary

In the real use-case one of my build targets would already be open when nob runs (the program that is executing) but it wouldn't need rebuild, so nob could just skip it, while rebuilding anything else that needs to be rebuilt (the shared library that is hot-reloaded).

@fartsicle

Copy link
Copy Markdown

I believe I have a better fix here: #272

Note the solution in this PR doesn't fix the sharing exclusive problem I describe in mine.

Second, although the MSDN doc you cite is generally correct, and in fact it might work on many Windows implementations, the official documentation for GetFileTime says the handle needs to have been created with GENERIC_READ, hence why I opted to err on the side of keeping GENERIC_READ in there.

This is actually really more a sharing permission problem, and I believe my PR should fix your error as well. Note how the error says "because it is being used by another process."

--Fart

@eduardomosko

Copy link
Copy Markdown
Author

That's surprising to me, it makes me wonder what kind of metadata setting dwDesiredAccess to 0 is supposed to let you access if not the time. But the documentation is quite clear, tomorrow I will check if your solution works for my problem (seems like it would), and adopt it in my local version of nob if it does

@fartsicle

Copy link
Copy Markdown

Agreed, it's surprising. Honestly I kept looking into it / thinking about it and it's highly possible that the documentation note about GetFileTime is an MSDN errata and passing in 0 would work generally with GetFileTime. There's no fundamental reason I can think of for it needing GENERIC_READ. But maybe they messed it up on some Windows implementations and hide it behind that, who knows.

If all else / just to be sure, one can also use GetFileInformationByHandle whose documentation has no mention of a requirement like this it seems, although it's a fatter query.

What I was getting at before I now realize was thrown off by this MSDN comment that had me keep the GENERIC_READ, and I'd like to fix that a bit. I was saying earlier that the sharing problem is separate in a sense but is the more invasive/"fundamental" problem here. But if one connects the dots of the MSDN documentation, then actually the "CreateFile(.., 0, 0, ..)" case is specially designed to not to trigger the sharing problem. The reason this works is all spread out through the CreateFileA docs and I'm on my phone so piecing it together here with links is a bit much, but eventually a logic like this becomes clear:

0, 0 ends up with no "real" file accesses from the handle, and only "real" file accesses trigger validating the exclusive share, and crucially 0, 0 is basically implied from other doc statements to specially not cause CreateFileA to validate the exclusive share initially either.

Horrifying.

So adding the other share permissions like in my PR would be technically redundant unless you want to both trust MSDN's GENERIC_READ requirement and use GetFileTime.

The "ideal" best fix is actually this PR's: with both 0'd + not trusting MSDN + using GetFileTime.

Otherwise you can either use GetFileTime but pass GENERIC_READ & the share permissions like in my PR to be safe.

Or you can use 0,0 + the fatter GetFileInformationByHandle

You can also use 0, (the three permissions flags) but that would technically be redundant.

All of these should fix it in my books, unless you think one should heed MSDN, then choose accordingly.

--Fart

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