Skip to content

Commit 89d403c

Browse files
committed
win32: enable p_utimes for readonly files
Instead of failing to set the timestamp of a read-only file (like any object file), set it writable temporarily to update the timestamp.
1 parent 7ece906 commit 89d403c

File tree

3 files changed

+160
-88
lines changed

3 files changed

+160
-88
lines changed

src/posix.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
#define _S_IFLNK S_IFLNK
2525
#endif
2626

27+
#ifndef S_IWUSR
28+
#define S_IWUSR 00200
29+
#endif
30+
2731
#ifndef S_IXUSR
2832
#define S_IXUSR 00100
2933
#endif

src/win32/posix_w32.c

Lines changed: 131 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -363,44 +363,6 @@ int p_lstat_posixly(const char *filename, struct stat *buf)
363363
return do_lstat(filename, buf, true);
364364
}
365365

366-
int p_utimes(const char *filename, const struct p_timeval times[2])
367-
{
368-
int fd, error;
369-
370-
if ((fd = p_open(filename, O_RDWR)) < 0)
371-
return fd;
372-
373-
error = p_futimes(fd, times);
374-
375-
close(fd);
376-
return error;
377-
}
378-
379-
int p_futimes(int fd, const struct p_timeval times[2])
380-
{
381-
HANDLE handle;
382-
FILETIME atime = {0}, mtime = {0};
383-
384-
if (times == NULL) {
385-
SYSTEMTIME st;
386-
387-
GetSystemTime(&st);
388-
SystemTimeToFileTime(&st, &atime);
389-
SystemTimeToFileTime(&st, &mtime);
390-
} else {
391-
git_win32__timeval_to_filetime(&atime, times[0]);
392-
git_win32__timeval_to_filetime(&mtime, times[1]);
393-
}
394-
395-
if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
396-
return -1;
397-
398-
if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
399-
return -1;
400-
401-
return 0;
402-
}
403-
404366
int p_readlink(const char *path, char *buf, size_t bufsiz)
405367
{
406368
git_win32_path path_w, target_w;
@@ -433,23 +395,69 @@ int p_symlink(const char *old, const char *new)
433395
return git_futils_fake_symlink(old, new);
434396
}
435397

398+
struct open_opts {
399+
DWORD access;
400+
DWORD sharing;
401+
SECURITY_ATTRIBUTES security;
402+
DWORD creation_disposition;
403+
DWORD attributes;
404+
int osf_flags;
405+
};
406+
407+
GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode)
408+
{
409+
memset(opts, 0, sizeof(struct open_opts));
410+
411+
switch (flags & (O_WRONLY | O_RDWR)) {
412+
case O_WRONLY:
413+
opts->access = GENERIC_WRITE;
414+
break;
415+
case O_RDWR:
416+
opts->access = GENERIC_READ | GENERIC_WRITE;
417+
break;
418+
default:
419+
opts->access = GENERIC_READ;
420+
break;
421+
}
422+
423+
opts->sharing = (DWORD)git_win32__createfile_sharemode;
424+
425+
switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) {
426+
case O_CREAT | O_EXCL:
427+
case O_CREAT | O_TRUNC | O_EXCL:
428+
opts->creation_disposition = CREATE_NEW;
429+
break;
430+
case O_CREAT | O_TRUNC:
431+
opts->creation_disposition = CREATE_ALWAYS;
432+
break;
433+
case O_TRUNC:
434+
opts->creation_disposition = TRUNCATE_EXISTING;
435+
break;
436+
case O_CREAT:
437+
opts->creation_disposition = OPEN_ALWAYS;
438+
break;
439+
default:
440+
opts->creation_disposition = OPEN_EXISTING;
441+
break;
442+
}
443+
444+
opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ?
445+
FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL;
446+
opts->osf_flags = flags & (O_RDONLY | O_APPEND);
447+
448+
opts->security.nLength = sizeof(SECURITY_ATTRIBUTES);
449+
opts->security.lpSecurityDescriptor = NULL;
450+
opts->security.bInheritHandle = 0;
451+
}
452+
436453
GIT_INLINE(int) open_once(
437454
const wchar_t *path,
438-
DWORD access,
439-
DWORD sharing,
440-
DWORD creation_disposition,
441-
DWORD attributes,
442-
int osf_flags)
455+
struct open_opts *opts)
443456
{
444-
SECURITY_ATTRIBUTES security;
445457
int fd;
446458

447-
security.nLength = sizeof(SECURITY_ATTRIBUTES);
448-
security.lpSecurityDescriptor = NULL;
449-
security.bInheritHandle = 0;
450-
451-
HANDLE handle = CreateFileW(path, access, sharing, &security,
452-
creation_disposition, attributes, 0);
459+
HANDLE handle = CreateFileW(path, opts->access, opts->sharing,
460+
&opts->security, opts->creation_disposition, opts->attributes, 0);
453461

454462
if (handle == INVALID_HANDLE_VALUE) {
455463
if (last_error_retryable())
@@ -459,14 +467,17 @@ GIT_INLINE(int) open_once(
459467
return -1;
460468
}
461469

462-
return _open_osfhandle((intptr_t)handle, osf_flags);
470+
if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0)
471+
CloseHandle(handle);
472+
473+
return fd;
463474
}
464475

465476
int p_open(const char *path, int flags, ...)
466477
{
467478
git_win32_path wpath;
468479
mode_t mode = 0;
469-
DWORD access, sharing, creation, attributes, osf_flags;
480+
struct open_opts opts = {0};
470481

471482
if (git_win32_path_from_utf8(wpath, path) < 0)
472483
return -1;
@@ -479,51 +490,83 @@ int p_open(const char *path, int flags, ...)
479490
va_end(arg_list);
480491
}
481492

482-
switch (flags & (O_WRONLY | O_RDWR)) {
483-
case O_WRONLY:
484-
access = GENERIC_WRITE;
485-
break;
486-
case O_RDWR:
487-
access = GENERIC_READ | GENERIC_WRITE;
488-
break;
489-
default:
490-
access = GENERIC_READ;
491-
break;
493+
open_opts_from_posix(&opts, flags, mode);
494+
495+
do_with_retries(
496+
open_once(wpath, &opts),
497+
0);
498+
}
499+
500+
int p_creat(const char *path, mode_t mode)
501+
{
502+
return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
503+
}
504+
505+
int p_utimes(const char *path, const struct p_timeval times[2])
506+
{
507+
git_win32_path wpath;
508+
int fd, error;
509+
DWORD attrs_orig, attrs_new = 0;
510+
struct open_opts opts = { 0 };
511+
512+
if (git_win32_path_from_utf8(wpath, path) < 0)
513+
return -1;
514+
515+
attrs_orig = GetFileAttributesW(wpath);
516+
517+
if (attrs_orig & FILE_ATTRIBUTE_READONLY) {
518+
attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY;
519+
520+
if (!SetFileAttributesW(wpath, attrs_new)) {
521+
giterr_set(GITERR_OS, "failed to set attributes");
522+
return -1;
523+
}
492524
}
493525

494-
sharing = (DWORD)git_win32__createfile_sharemode;
526+
open_opts_from_posix(&opts, O_RDWR, 0);
495527

496-
switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) {
497-
case O_CREAT | O_EXCL:
498-
case O_CREAT | O_TRUNC | O_EXCL:
499-
creation = CREATE_NEW;
500-
break;
501-
case O_CREAT | O_TRUNC:
502-
creation = CREATE_ALWAYS;
503-
break;
504-
case O_TRUNC:
505-
creation = TRUNCATE_EXISTING;
506-
break;
507-
case O_CREAT:
508-
creation = OPEN_ALWAYS;
509-
break;
510-
default:
511-
creation = OPEN_EXISTING;
512-
break;
528+
if ((fd = open_once(wpath, &opts)) < 0) {
529+
error = -1;
530+
goto done;
513531
}
514532

515-
attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ?
516-
FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL;
517-
osf_flags = flags & (O_RDONLY | O_APPEND);
533+
error = p_futimes(fd, times);
534+
close(fd);
518535

519-
do_with_retries(
520-
open_once(wpath, access, sharing, creation, attributes, osf_flags),
521-
0);
536+
done:
537+
if (attrs_orig != attrs_new) {
538+
DWORD os_error = GetLastError();
539+
SetFileAttributesW(wpath, attrs_orig);
540+
SetLastError(os_error);
541+
}
542+
543+
return error;
522544
}
523545

524-
int p_creat(const char *path, mode_t mode)
546+
int p_futimes(int fd, const struct p_timeval times[2])
525547
{
526-
return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
548+
HANDLE handle;
549+
FILETIME atime = { 0 }, mtime = { 0 };
550+
551+
if (times == NULL) {
552+
SYSTEMTIME st;
553+
554+
GetSystemTime(&st);
555+
SystemTimeToFileTime(&st, &atime);
556+
SystemTimeToFileTime(&st, &mtime);
557+
}
558+
else {
559+
git_win32__timeval_to_filetime(&atime, times[0]);
560+
git_win32__timeval_to_filetime(&mtime, times[1]);
561+
}
562+
563+
if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
564+
return -1;
565+
566+
if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
567+
return -1;
568+
569+
return 0;
527570
}
528571

529572
int p_getcwd(char *buffer_out, size_t size)

tests/odb/freshen.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ void test_odb_freshen__loose_blob(void)
5555
cl_assert(before.st_mtime < after.st_mtime);
5656
}
5757

58+
#define UNIQUE_STR "doesnt exist in the odb yet\n"
59+
#define UNIQUE_BLOB_ID "78a87d0b8878c5953b9a63015ff4e22a3d898826"
60+
#define UNIQUE_BLOB_FN "78/a87d0b8878c5953b9a63015ff4e22a3d898826"
61+
62+
void test_odb_freshen__readonly_object(void)
63+
{
64+
git_oid expected_id, id;
65+
struct stat before, after;
66+
67+
cl_git_pass(git_oid_fromstr(&expected_id, UNIQUE_BLOB_ID));
68+
69+
cl_git_pass(git_blob_create_frombuffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR)));
70+
cl_assert_equal_oid(&expected_id, &id);
71+
72+
set_time_wayback(&before, UNIQUE_BLOB_FN);
73+
cl_assert((before.st_mode & S_IWUSR) == 0);
74+
75+
cl_git_pass(git_blob_create_frombuffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR)));
76+
cl_assert_equal_oid(&expected_id, &id);
77+
cl_must_pass(p_lstat("testrepo.git/objects/" UNIQUE_BLOB_FN, &after));
78+
79+
cl_assert(before.st_atime < after.st_atime);
80+
cl_assert(before.st_mtime < after.st_mtime);
81+
}
82+
5883
#define LOOSE_TREE_ID "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"
5984
#define LOOSE_TREE_FN "94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162"
6085

0 commit comments

Comments
 (0)