Stop reading json files twice
authorBranimir Ri\v{c}ko <rickobranimir@gmail.com>
Sat, 1 Jun 2024 22:38:06 +0000 (00:38 +0200)
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>
Mon, 9 Sep 2024 21:37:31 +0000 (15:37 -0600)
Split out the JSON file opening and reading logic into a Windows
and Posix function to allow only reading the contents of a file
once.

loader/cJSON.c

index b07d9518a1909701393b33c597e55a84d011944f..dd875853b7ebf1151b3c5441868dd9cce040851b 100644 (file)
 #include "loader.h"
 #include "log.h"
 
+#if COMMON_UNIX_PLATFORMS
+#include <fcntl.h>
+#include <sys/stat.h>
+#endif
+
 static void *cJSON_malloc(const VkAllocationCallbacks *pAllocator, size_t size) {
     return loader_calloc(pAllocator, size, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
 }
@@ -951,65 +956,105 @@ cJSON *loader_cJSON_GetObjectItem(cJSON *object, const char *string) {
     return c;
 }
 
-VkResult loader_get_json(const struct loader_instance *inst, const char *filename, cJSON **json) {
-    FILE *file = NULL;
-    char *json_buf = NULL;
-    size_t len;
+#ifdef _WIN32
+static VkResult loader_read_entire_file(const struct loader_instance *inst, const char *filename, char **out_buff) {
+    HANDLE file_handle = INVALID_HANDLE_VALUE;
+    DWORD len = 0, read_len = 0;
     VkResult res = VK_SUCCESS;
+    BOOL read_ok = false;
 
-    assert(json != NULL);
-
-    *json = NULL;
-
-#if defined(_WIN32)
     int filename_utf16_size = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
     if (filename_utf16_size > 0) {
         wchar_t *filename_utf16 = (wchar_t *)loader_stack_alloc(filename_utf16_size * sizeof(wchar_t));
         if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, filename_utf16, filename_utf16_size) == filename_utf16_size) {
-            errno_t wfopen_error = _wfopen_s(&file, filename_utf16, L"rb");
-            if (0 != wfopen_error) {
-                loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to open JSON file %s", filename);
-            }
+            file_handle =
+                CreateFileW(filename_utf16, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
         }
     }
-#elif COMMON_UNIX_PLATFORMS
-    file = fopen(filename, "rb");
-#else
-#warning fopen not available on this platform
-#endif
-
-    if (!file) {
+    if (INVALID_HANDLE_VALUE == file_handle) {
         loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to open JSON file %s", filename);
         res = VK_ERROR_INITIALIZATION_FAILED;
         goto out;
     }
-    // NOTE: We can't just use fseek(file, 0, SEEK_END) because that isn't guaranteed to be supported on all systems
-    size_t fread_ret_count = 0;
-    do {
-        char buffer[256];
-        fread_ret_count = fread(buffer, 1, 256, file);
-    } while (fread_ret_count == 256 && !feof(file));
-    len = ftell(file);
-    fseek(file, 0, SEEK_SET);
-    json_buf = (char *)loader_instance_heap_calloc(inst, len + 1, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
-    if (json_buf == NULL) {
-        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0,
-                   "loader_get_json: Failed to allocate space for JSON file %s buffer of length %lu", filename, len);
+    len = GetFileSize(file_handle, NULL);
+    if (INVALID_FILE_SIZE == len) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to read file size of JSON file %s", filename);
+        res = VK_ERROR_INITIALIZATION_FAILED;
+        goto out;
+    }
+    *out_buff = (char *)loader_instance_heap_calloc(inst, len + 1, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
+    if (NULL == *out_buff) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to allocate memory to read JSON file %s", filename);
         res = VK_ERROR_OUT_OF_HOST_MEMORY;
         goto out;
     }
-    if (fread(json_buf, sizeof(char), len, file) != len) {
-        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to read JSON file %s.", filename);
+    read_ok = ReadFile(file_handle, *out_buff, len, &read_len, NULL);
+    if (len != read_len || false == read_ok) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to read entire JSON file %s", filename);
         res = VK_ERROR_INITIALIZATION_FAILED;
         goto out;
     }
-    json_buf[len] = '\0';
+    (*out_buff)[len] = '\0';
 
-    // Can't be a valid json if the string is of length zero
-    if (len == 0) {
+out:
+    if (INVALID_HANDLE_VALUE != file_handle) {
+        CloseHandle(file_handle);
+    }
+    return res;
+}
+#elif COMMON_UNIX_PLATFORMS
+static VkResult loader_read_entire_file(const struct loader_instance *inst, const char *filename, char **out_buff) {
+    FILE *file = NULL;
+    struct stat stats = {0};
+    VkResult res = VK_SUCCESS;
+
+    file = fopen(filename, "rb");
+    if (NULL == file) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to open JSON file %s", filename);
+        res = VK_ERROR_INITIALIZATION_FAILED;
+        goto out;
+    }
+    if (-1 == fstat(fileno(file), &stats)) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to read file size of JSON file %s", filename);
+        res = VK_ERROR_INITIALIZATION_FAILED;
+        goto out;
+    }
+    *out_buff = (char *)loader_instance_heap_calloc(inst, stats.st_size + 1, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
+    if (NULL == *out_buff) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to allocate memory to read JSON file %s", filename);
+        res = VK_ERROR_OUT_OF_HOST_MEMORY;
+        goto out;
+    }
+    if (stats.st_size != (long int)fread(*out_buff, sizeof(char), stats.st_size, file)) {
+        loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_get_json: Failed to read entire JSON file %s", filename);
         res = VK_ERROR_INITIALIZATION_FAILED;
         goto out;
     }
+    (*out_buff)[stats.st_size] = '\0';
+
+out:
+    if (NULL != file) {
+        fclose(file);
+    }
+    return res;
+}
+#else
+#warning fopen not available on this platform
+VkResult loader_read_entire_file(const struct loader_instance *inst, const char *filename, char **out_buff) {
+    return VK_ERROR_INITIALIZATION_FAILED;
+}
+#endif
+
+VkResult loader_get_json(const struct loader_instance *inst, const char *filename, cJSON **json) {
+    char *json_buf = NULL;
+    VkResult res = VK_SUCCESS;
+
+    assert(json != NULL);
+
+    *json = NULL;
+    res = loader_read_entire_file(inst, filename, &json_buf);
+    if (VK_SUCCESS != res) goto out;
+
     // Parse text from file
     bool out_of_memory = false;
     *json = cJSON_Parse(inst ? &inst->alloc_callbacks : NULL, json_buf, &out_of_memory);
@@ -1025,9 +1070,6 @@ VkResult loader_get_json(const struct loader_instance *inst, const char *filenam
 
 out:
     loader_instance_heap_free(inst, json_buf);
-    if (NULL != file) {
-        fclose(file);
-    }
     if (res != VK_SUCCESS && *json != NULL) {
         loader_cJSON_Delete(*json);
         *json = NULL;