Support rename with SetFileInformationByHandle

Implement SetFileInformationByHandle when class=FileRenameInfo.

Recent clang-cl.exe has started to use SetFileInformationByHandle to
rename a file.

info is FILE_RENAME_INFORMATION. FileName contains the path to rename.
It might be absolute path, or might be relative path from CWD.

Bug: b/67663665
Change-Id: I0ea96b87dff304713344efaa8a66efa0d327b734
diff --git a/dlls/kernel32/file.c b/dlls/kernel32/file.c
index d61c56c..cf08634 100644
--- a/dlls/kernel32/file.c
+++ b/dlls/kernel32/file.c
@@ -1040,10 +1040,121 @@
     return FALSE;
 }
 
+BOOL SetFileInformationByHandle_FileRenameInfo(HANDLE file,
+                                               FILE_RENAME_INFORMATION *fri,
+                                               DWORD size) {
+  // forward declaration.
+  DWORD WINAPI GetFinalPathNameByHandleW(HANDLE file, LPWSTR path, DWORD charcount, DWORD flags);
+
+  BOOL ret = FALSE;
+  DWORD error = ERROR_INVALID_PARAMETER;
+
+  WCHAR src_path[MAX_PATH + 1], dst_path[MAX_PATH + 1];
+  UNICODE_STRING nt_src_path, nt_dst_path;
+  ANSI_STRING unix_src_path, unix_dst_path;
+  DWORD len, disposition;
+  HANDLE hDstFile = INVALID_HANDLE_VALUE;
+
+  nt_src_path.Buffer = NULL;
+  nt_dst_path.Buffer = NULL;
+  unix_src_path.Buffer = NULL;
+  unix_dst_path.Buffer = NULL;
+
+  // Takes the src file information, and create nt_src_path and unix_src_path.
+  len = GetFinalPathNameByHandleW(file, src_path, MAX_PATH, 0);
+  if (len == 0 || len > MAX_PATH) {
+    // When GetFinalPathNameByHandleW failed, it returns 0 or more than MAX_PATH
+    // when it requires more buffer.
+    goto done;
+  }
+  // src_path will be something like L"\\\\?\\C:\\Temp\\hello-f5751ef4.obj.tmp"
+  TRACE("(shinyak) src_path=%s\n", debugstr_w(src_path));
+  if (!RtlDosPathNameToNtPathName_U(src_path, &nt_src_path, NULL, NULL)) {
+    TRACE("(shinyak) failed to convert %s to ntpath", debugstr_w(src_path));
+    goto done;
+  }
+  if (wine_nt_to_unix_file_name(&nt_src_path, &unix_src_path, FILE_OPEN, FALSE) != STATUS_SUCCESS) {
+    TRACE("(shinyak) failed to convert src_path=%s\n", debugstr_w(src_path));
+    goto done;
+  }
+
+  // Open dst file. If the dst file exists, we're able to take unix name.
+  // If `Replace` is false and the same name file exists, CreateFileW will fail.
+  disposition = fri->Replace ? CREATE_ALWAYS : CREATE_NEW;
+  hDstFile = CreateFileW(fri->FileName,
+                         GENERIC_READ | GENERIC_WRITE,
+                         0,
+                         NULL,
+                         disposition,
+                         FILE_ATTRIBUTE_NORMAL,
+                         NULL);
+  if (hDstFile == INVALID_HANDLE_VALUE) {
+    TRACE("(shinyak) failed to open %s\n", fri->FileName);
+    goto done;
+  }
+
+  len = GetFinalPathNameByHandleW(hDstFile, dst_path, MAX_PATH, 0);
+  if (len == 0 || len > MAX_PATH) {
+    goto done;
+  }
+  TRACE("(shinyak) dst_path=%s\n", debugstr_w(dst_path));
+  if (!RtlDosPathNameToNtPathName_U(dst_path, &nt_dst_path, NULL, NULL)) {
+    TRACE("(shinyak) failed to convert %s to ntpath", debugstr_w(dst_path));
+    goto done;
+  }
+  if (wine_nt_to_unix_file_name(&nt_dst_path, &unix_dst_path, FILE_OPEN, FALSE) != STATUS_SUCCESS) {
+    TRACE("(shinyak) failed to convert src_path=%s\n", debugstr_w(dst_path));
+    goto done;
+  }
+
+  // Now, we have unix filename for src and dst.
+  // Call `rename` like ReplaceFileW.
+  if (rename(unix_src_path.Buffer, unix_dst_path.Buffer) < 0) {
+    error = ERROR_UNABLE_TO_REMOVE_REPLACED;  // Is this correct?
+    goto done;
+  }
+
+  ret = TRUE;
+
+done:
+  RtlFreeUnicodeString(&nt_src_path);
+  RtlFreeUnicodeString(&nt_dst_path);
+  RtlFreeAnsiString(&unix_src_path);
+  RtlFreeAnsiString(&unix_dst_path);
+
+  if (hDstFile != INVALID_HANDLE_VALUE)
+    CloseHandle(hDstFile);
+  if (!ret)
+    SetLastError(error);
+  return ret;
+}
+
 BOOL WINAPI SetFileInformationByHandle( HANDLE file, FILE_INFO_BY_HANDLE_CLASS class, VOID *info, DWORD size )
 {
-    FIXME("%p %u %p %u - stub\n", file, class, info, size);
-    return FALSE;
+  if (class == FileRenameInfo) {
+    // The actual content of info. (xx) is added by me.
+    // ( 8) 01 00 00 00 00 00 00 00
+    // ( 8) 00 00 00 00 00 00 00 00
+    // ( 4) 09 00 00 00
+    // (20) 68 00 65 00 6C 00 6C 00 6F 00 2E 00 6F 00 62 00 6A 00 00 00
+    //     ( h     e     l     l     o     .     o     b     j    \0   )
+    //
+    // This is FILE_RENAME_INFORMATION described in winternl.h.
+    // The actual info seems like the following
+    //   ReplaceIfExists: 8 byte (BOOLEAN 1 byte + 7byte padding)
+    //   RootDirectory:   8 byte (HANDLE)
+    //   FileNameLength:  4 byte (ULONG)
+    //   FileName:        WCHAR array
+    // and this is different from FILE_RENAME_INFO described in
+    // https://msdn.microsoft.com/en-us/library/windows/desktop/aa364398(v=vs.85).aspx
+    //
+
+    FILE_RENAME_INFORMATION *fri = (FILE_RENAME_INFORMATION *)(info);
+    return SetFileInformationByHandle_FileRenameInfo(file, fri, size);
+  }
+
+  FIXME("%p %u %p %u - stub\n", file, class, info, size);
+  return FALSE;
 }
 
 /***********************************************************************