Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nob.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const char *test_names[] = {
"bytes_for_utf8",
"sv_foreach",
"svlit_at_different_storages",
"needs_rebuild_locked_file",
};
#define test_names_count ARRAY_LEN(test_names)

Expand Down
41 changes: 17 additions & 24 deletions nob.h
Original file line number Diff line number Diff line change
Expand Up @@ -2459,41 +2459,34 @@ NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv)
NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count)
{
#ifdef _WIN32
BOOL bSuccess;

HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (output_path_fd == INVALID_HANDLE_VALUE) {
// NOTE: GetFileAttributesEx() reads only the file metadata and never
// opens the file, so it cannot fail just because an editor or an
// antivirus has the file open. CreateFile() with dwShareMode=0 used to
// fail with ERROR_SHARING_VIOLATION in that case, which the POSIX
// stat() implementation below is not susceptible to.
WIN32_FILE_ATTRIBUTE_DATA file_attributes = {0};

if (GetFileAttributesExA(output_path, GetFileExInfoStandard, &file_attributes) == 0) {
// NOTE: if output does not exist it 100% must be rebuilt
if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError()));
return -1;
}
FILETIME output_path_time;
bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time);
CloseHandle(output_path_fd);
if (!bSuccess) {
nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError()));
if (GetLastError() == ERROR_FILE_NOT_FOUND)
return 1;
nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", output_path, nob_win32_error_message(GetLastError()));
return -1;
}
FILETIME output_path_time = file_attributes.ftLastWriteTime;

for (size_t i = 0; i < input_paths_count; ++i) {
const char *input_path = input_paths[i];
HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (input_path_fd == INVALID_HANDLE_VALUE) {
if (GetFileAttributesExA(input_path, GetFileExInfoStandard, &file_attributes) == 0) {
// NOTE: non-existing input is an error cause it is needed for building in the first place
nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError()));
return -1;
}
FILETIME input_path_time;
bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time);
CloseHandle(input_path_fd);
if (!bSuccess) {
nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError()));
nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", input_path, nob_win32_error_message(GetLastError()));
return -1;
}
FILETIME input_path_time = file_attributes.ftLastWriteTime;

// NOTE: if even a single input_path is fresher than output_path that's 100% rebuild
if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1;
if (CompareFileTime(&input_path_time, &output_path_time) == 1)
return 1;
}

return 0;
Expand Down
51 changes: 51 additions & 0 deletions tests/needs_rebuild_locked_file.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "shared.h"

#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#endif

int main(void)
{
// The harness runs tests in a fresh temp cwd.
const char *output_path = "output.txt";
const char *input_path = "input.txt";

// Write the output first, then the input, so the input is strictly
// fresher than the output and needs_rebuild() must return 1.
if (!write_entire_file(output_path, "output", 6)) return 1;

// Make sure the input file gets a strictly fresher timestamp than the
// output file regardless of the filesystem timestamp resolution.
#ifdef _WIN32
Sleep(100);
#else
struct timespec ts = {0, 100000000};
nanosleep(&ts, NULL);
#endif

if (!write_entire_file(input_path, "input", 5)) return 1;

#ifdef _WIN32
// Hold the input file the way an editor or an AV scanner would. With
// dwShareMode = 0 nobody else may open it, not even for reading, so
// needs_rebuild() must not rely on opening the file.
HANDLE lock = CreateFileA(input_path, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (lock == INVALID_HANDLE_VALUE) {
fprintf(stderr, "could not lock %s (err=%lu)\n", input_path, GetLastError());
return 1;
}
#endif // _WIN32

// Must NOT fail just because the input file is locked.
int result = needs_rebuild(output_path, &input_path, 1);
printf("needs_rebuild = %d\n", result);

#ifdef _WIN32
CloseHandle(lock);
#endif // _WIN32

return 0;
}
1 change: 1 addition & 0 deletions tests/needs_rebuild_locked_file.stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
needs_rebuild = 1
1 change: 1 addition & 0 deletions tests/needs_rebuild_locked_file.win32.stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
needs_rebuild = 1