rename() with overwrite semantics on every platform: POSIX rename already * replaces atomically; Windows rename fails with EEXIST when the target * exists, so use write-through MoveFileExW(MOVEFILE_REPLACE_EXISTING) there * (wide paths — raw MoveFileExA would re-mangle non-ASCII cache paths). */
| 1034 | * exists, so use write-through MoveFileExW(MOVEFILE_REPLACE_EXISTING) there |
| 1035 | * (wide paths — raw MoveFileExA would re-mangle non-ASCII cache paths). */ |
| 1036 | int cbm_rename_replace(const char *src, const char *dst) { |
| 1037 | #ifdef _WIN32 |
| 1038 | wchar_t *wsrc = cbm_path_to_wide(src); |
| 1039 | wchar_t *wdst = cbm_path_to_wide(dst); |
| 1040 | int ret = CBM_NOT_FOUND; |
| 1041 | if (wsrc && wdst) { |
| 1042 | if (MoveFileExW(wsrc, wdst, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { |
| 1043 | ret = 0; |
| 1044 | } else { |
| 1045 | /* Translate the Win32 error into errno so callers can report WHY. |
| 1046 | * |
| 1047 | * Callers log `errno` after a failed rename (see |
| 1048 | * finalize.rename_failed in the pipeline). Without this the value |
| 1049 | * is whatever happened to be left there by an unrelated CRT call, |
| 1050 | * so on Windows the one field that should explain an atomic-publish |
| 1051 | * failure was noise. #1620 is exactly that: an ACL problem surfaced |
| 1052 | * to the user as "Pipeline failed. Check repo_path exists and |
| 1053 | * contains source files" — blaming their repository — because |
| 1054 | * ERROR_ACCESS_DENIED never reached the log. |
| 1055 | * |
| 1056 | * ERROR_ACCESS_DENIED is the interesting one here: MoveFileEx needs |
| 1057 | * DELETE on the destination, which a cache file created under an |
| 1058 | * empty or foreign DACL does not grant. */ |
| 1059 | DWORD error = GetLastError(); |
| 1060 | switch (error) { |
| 1061 | case ERROR_ACCESS_DENIED: |
| 1062 | case ERROR_WRITE_PROTECT: |
| 1063 | errno = EACCES; |
| 1064 | break; |
| 1065 | case ERROR_FILE_NOT_FOUND: |
| 1066 | case ERROR_PATH_NOT_FOUND: |
| 1067 | errno = ENOENT; |
| 1068 | break; |
| 1069 | case ERROR_SHARING_VIOLATION: |
| 1070 | case ERROR_LOCK_VIOLATION: |
| 1071 | case ERROR_USER_MAPPED_FILE: |
| 1072 | errno = EBUSY; |
| 1073 | break; |
| 1074 | case ERROR_NOT_SAME_DEVICE: |
| 1075 | errno = EXDEV; |
| 1076 | break; |
| 1077 | case ERROR_DISK_FULL: |
| 1078 | errno = ENOSPC; |
| 1079 | break; |
| 1080 | case ERROR_INVALID_NAME: |
| 1081 | case ERROR_FILENAME_EXCED_RANGE: |
| 1082 | errno = ENAMETOOLONG; |
| 1083 | break; |
| 1084 | default: |
| 1085 | errno = EIO; |
| 1086 | break; |
| 1087 | } |
| 1088 | ret = CBM_NOT_FOUND; |
| 1089 | } |
| 1090 | } |
| 1091 | free(wsrc); |
| 1092 | free(wdst); |
| 1093 | return ret; |