1 /**************************************************************************
3 * Copyright 2013-2014 RAD Game Tools and Valve Software
4 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 **************************************************************************/
27 // File: vogl_file_utils.cpp
29 #include "vogl_core.h"
30 #include "vogl_file_utils.h"
31 #include "vogl_strutils.h"
32 #include "vogl_cfile_stream.h"
33 #include "vogl_console.h"
35 #ifdef VOGL_USE_WIN32_API
36 #include "vogl_winhdr.h"
50 #ifdef VOGL_USE_WIN32_API
51 bool file_utils::is_read_only(const char *pFilename)
53 uint32 dst_file_attribs = GetFileAttributesA(pFilename);
54 if (dst_file_attribs == INVALID_FILE_ATTRIBUTES)
56 if (dst_file_attribs & FILE_ATTRIBUTE_READONLY)
61 bool file_utils::disable_read_only(const char *pFilename)
63 uint32 dst_file_attribs = GetFileAttributesA(pFilename);
64 if (dst_file_attribs == INVALID_FILE_ATTRIBUTES)
66 if (dst_file_attribs & FILE_ATTRIBUTE_READONLY)
68 dst_file_attribs &= ~FILE_ATTRIBUTE_READONLY;
69 if (SetFileAttributesA(pFilename, dst_file_attribs))
75 bool file_utils::is_older_than(const char *pSrcFilename, const char *pDstFilename)
77 WIN32_FILE_ATTRIBUTE_DATA src_file_attribs;
78 const BOOL src_file_exists = GetFileAttributesExA(pSrcFilename, GetFileExInfoStandard, &src_file_attribs);
80 WIN32_FILE_ATTRIBUTE_DATA dst_file_attribs;
81 const BOOL dest_file_exists = GetFileAttributesExA(pDstFilename, GetFileExInfoStandard, &dst_file_attribs);
83 if ((dest_file_exists) && (src_file_exists))
85 LONG timeComp = CompareFileTime(&src_file_attribs.ftLastWriteTime, &dst_file_attribs.ftLastWriteTime);
92 bool file_utils::does_file_exist(const char *pFilename)
94 const DWORD fullAttributes = GetFileAttributesA(pFilename);
96 if (fullAttributes == INVALID_FILE_ATTRIBUTES)
99 if (fullAttributes & FILE_ATTRIBUTE_DIRECTORY)
105 bool file_utils::does_dir_exist(const char *pDir)
107 //-- Get the file attributes.
108 DWORD fullAttributes = GetFileAttributesA(pDir);
110 if (fullAttributes == INVALID_FILE_ATTRIBUTES)
113 if (fullAttributes & FILE_ATTRIBUTE_DIRECTORY)
119 bool file_utils::get_file_size(const char *pFilename, uint64_t &file_size)
123 WIN32_FILE_ATTRIBUTE_DATA attr;
125 if (0 == GetFileAttributesExA(pFilename, GetFileExInfoStandard, &attr))
128 if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
131 file_size = static_cast<uint64_t>(attr.nFileSizeLow) | (static_cast<uint64_t>(attr.nFileSizeHigh) << 32U);
135 #elif defined(__GNUC__)
136 bool file_utils::is_read_only(const char *pFilename)
138 VOGL_NOTE_UNUSED(pFilename);
139 console::debug("%s: Unimplemented\n", VOGL_FUNCTION_NAME);
143 bool file_utils::disable_read_only(const char *pFilename)
145 VOGL_NOTE_UNUSED(pFilename);
146 console::debug("%s: Unimplemented\n", VOGL_FUNCTION_NAME);
150 bool file_utils::is_older_than(const char *pSrcFilename, const char *pDstFilename)
152 VOGL_NOTE_UNUSED(pSrcFilename);
153 VOGL_NOTE_UNUSED(pDstFilename);
154 console::debug("%s: Unimplemented\n", VOGL_FUNCTION_NAME);
158 bool file_utils::does_file_exist(const char *pFilename)
160 struct stat64 stat_buf;
161 int result = stat64(pFilename, &stat_buf);
166 if (S_ISREG(stat_buf.st_mode))
171 bool file_utils::does_dir_exist(const char *pDir)
173 struct stat64 stat_buf;
174 int result = stat64(pDir, &stat_buf);
177 if (S_ISDIR(stat_buf.st_mode) || S_ISLNK(stat_buf.st_mode))
182 bool file_utils::get_file_size(const char *pFilename, uint64_t &file_size)
185 struct stat64 stat_buf;
186 int result = stat64(pFilename, &stat_buf);
189 if (!S_ISREG(stat_buf.st_mode))
191 file_size = stat_buf.st_size;
195 bool file_utils::is_read_only(const char *pFilename)
200 bool file_utils::disable_read_only(const char *pFilename)
203 console::debug("%s: Unimplemented\n", VOGL_FUNCTION_NAME);
207 bool file_utils::is_older_than(const char *pSrcFilename, const char *pDstFilename)
209 console::debug("%s: Unimplemented\n", VOGL_FUNCTION_NAME);
213 bool file_utils::does_file_exist(const char *pFilename)
215 FILE *pFile = vogl_fopen(pFilename, "rb");
222 bool file_utils::does_dir_exist(const char *pDir)
224 console::debug("%s: Unimplemented\n", VOGL_FUNCTION_NAME);
228 bool file_utils::get_file_size(const char *pFilename, uint64_t &file_size)
230 FILE *pFile = vogl_fopen(pFilename, "rb");
233 vogl_fseek(pFile, 0, SEEK_END);
234 file_size = vogl_ftell(pFile);
240 bool file_utils::get_file_size(const char *pFilename, uint32 &file_size)
242 uint64_t file_size64;
243 if (!get_file_size(pFilename, file_size64))
249 if (file_size64 > cUINT32_MAX)
250 file_size64 = cUINT32_MAX;
252 file_size = static_cast<uint32>(file_size64);
256 void file_utils::delete_file(const char *pFilename)
261 bool file_utils::is_path_separator(char c)
264 return (c == '/') || (c == '\\');
270 bool file_utils::is_path_or_drive_separator(char c)
273 return (c == '/') || (c == '\\') || (c == ':');
279 bool file_utils::is_drive_separator(char c)
289 bool file_utils::split_path(const char *p, dynamic_string *pDrive, dynamic_string *pDir, dynamic_string *pFilename, dynamic_string *pExt)
294 char drive_buf[_MAX_DRIVE];
295 char dir_buf[_MAX_DIR];
296 char fname_buf[_MAX_FNAME];
297 char ext_buf[_MAX_EXT];
300 // Compiling with MSVC
301 errno_t error = _splitpath_s(p,
302 pDrive ? drive_buf : NULL, pDrive ? _MAX_DRIVE : 0,
303 pDir ? dir_buf : NULL, pDir ? _MAX_DIR : 0,
304 pFilename ? fname_buf : NULL, pFilename ? _MAX_FNAME : 0,
305 pExt ? ext_buf : NULL, pExt ? _MAX_EXT : 0);
309 // Compiling with MinGW
311 pDrive ? drive_buf : NULL,
312 pDir ? dir_buf : NULL,
313 pFilename ? fname_buf : NULL,
314 pExt ? ext_buf : NULL);
322 *pFilename = fname_buf;
328 strcpy_safe(dirtmp, sizeof(dirtmp), p);
329 strcpy_safe(nametmp, sizeof(nametmp), p);
334 const char *pDirName = dirname(dirtmp);
341 if ((!pDir->is_empty()) && (pDir->back() != '/'))
342 pDir->append_char('/');
345 const char *pBaseName = basename(nametmp);
351 pFilename->set(pBaseName);
352 remove_extension(*pFilename);
357 pExt->set(pBaseName);
358 get_extension(*pExt);
362 #endif // #ifdef WIN32
367 bool file_utils::split_path(const char *p, dynamic_string &path, dynamic_string &filename)
369 dynamic_string temp_drive, temp_path, temp_ext;
370 if (!split_path(p, &temp_drive, &temp_path, &filename, &temp_ext))
373 filename += temp_ext;
375 combine_path(path, temp_drive.get_ptr(), temp_path.get_ptr());
379 bool file_utils::get_pathname(const char *p, dynamic_string &path)
381 dynamic_string temp_drive, temp_path;
382 if (!split_path(p, &temp_drive, &temp_path, NULL, NULL))
385 combine_path(path, temp_drive.get_ptr(), temp_path.get_ptr());
389 bool file_utils::get_filename(const char *p, dynamic_string &filename)
391 dynamic_string temp_ext;
392 if (!split_path(p, NULL, NULL, &filename, &temp_ext))
395 filename += temp_ext;
399 void file_utils::combine_path(dynamic_string &dst, const char *pA, const char *pB)
401 dynamic_string temp(pA);
402 if ((!temp.is_empty()) && (!is_path_separator(pB[0])))
404 char c = temp[temp.get_len() - 1];
405 if (!is_path_separator(c))
406 temp.append_char(VOGL_PATH_SEPERATOR_CHAR);
412 void file_utils::combine_path(dynamic_string &dst, const char *pA, const char *pB, const char *pC)
414 combine_path(dst, pA, pB);
415 combine_path(dst, dst.get_ptr(), pC);
418 void file_utils::combine_path_and_extension(dynamic_string &dst, const char *pA, const char *pB, const char *pC, const char *pExt)
420 combine_path(dst, pA, pB, pC);
422 if ((!dst.ends_with(".")) && (pExt[0]) && (pExt[0] != '.'))
423 dst.append_char('.');
428 void file_utils::combine_path_and_extension(dynamic_string &dst, const dynamic_string *pA, const dynamic_string *pB, const dynamic_string *pC, const dynamic_string *pExt)
430 combine_path_and_extension(dst, pA ? pA->get_ptr() : "", pB ? pB->get_ptr() : "", pC ? pC->get_ptr() : "", pExt ? pExt->get_ptr() : "");
433 bool file_utils::full_path(dynamic_string &path)
437 char *p = _fullpath(buf, path.get_ptr(), sizeof(buf));
443 dynamic_string pn, fn;
444 split_path(path.get_ptr(), pn, fn);
445 if ((fn == ".") || (fn == ".."))
447 p = realpath(path.get_ptr(), buf);
456 p = realpath(pn.get_ptr(), buf);
459 combine_path(path, buf, fn.get_ptr());
466 bool file_utils::get_extension(dynamic_string &filename)
470 sep = filename.find_right('\\');
473 sep = filename.find_right('/');
475 int dot = filename.find_right('.');
482 filename.right(dot + 1);
487 bool file_utils::remove_extension(dynamic_string &filename)
491 sep = filename.find_right('\\');
494 sep = filename.find_right('/');
496 int dot = filename.find_right('.');
505 bool file_utils::create_directory(const char *pPath)
508 int status = _mkdir(pPath);
510 int status = mkdir(pPath, S_IRWXU | S_IRWXG | S_IRWXO);
513 console::debug("%s: Created directory %s\n", VOGL_FUNCTION_NAME, pPath);
517 bool file_utils::create_directories(const dynamic_string &path, bool remove_filename)
519 dynamic_string path_to_create(path);
521 full_path(path_to_create);
525 dynamic_string pn, fn;
526 split_path(path_to_create.get_ptr(), pn, fn);
530 return create_directories_from_full_path(path_to_create);
533 bool file_utils::create_directories_from_full_path(const dynamic_string &fullpath)
535 bool got_unc = false;
536 VOGL_NOTE_UNUSED(got_unc);
537 dynamic_string cur_path;
539 const int l = fullpath.get_len();
544 const char c = fullpath.get_ptr()[n];
546 const bool sep = is_path_separator(c);
547 const bool back_sep = is_path_separator(cur_path.back());
548 const bool is_last_char = (n == (l - 1));
550 if (((sep) && (!back_sep)) || (is_last_char))
552 if ((is_last_char) && (!sep))
553 cur_path.append_char(c);
555 bool valid = !cur_path.is_empty();
558 // reject obvious stuff (drives, beginning of UNC paths):
562 if ((cur_path.get_len() == 2) && (cur_path[1] == ':'))
564 else if ((cur_path.get_len() >= 2) && (cur_path[0] == '\\') && (cur_path[1] == '\\'))
570 else if (cur_path == "\\")
578 create_directory(cur_path.get_ptr());
582 cur_path.append_char(c);
590 void file_utils::trim_trailing_seperator(dynamic_string &path)
592 if ((path.get_len()) && (is_path_separator(path.back())))
593 path.truncate(path.get_len() - 1);
596 bool file_utils::add_default_extension(dynamic_string &path, const char *pExt)
598 dynamic_string ext(path);
608 // See http://www.codeproject.com/KB/string/wildcmp.aspx
609 int file_utils::wildcmp(const char *pWild, const char *pString)
611 const char *cp = NULL, *mp = NULL;
613 while ((*pString) && (*pWild != '*'))
615 if ((*pWild != *pString) && (*pWild != '?'))
621 // Either *pString=='\0' or *pWild='*' here.
632 else if ((*pWild == *pString) || (*pWild == '?'))
644 while (*pWild == '*')
650 bool file_utils::read_file_to_vec(const char *pPath, uint8_vec &data)
653 void *p = read_file_to_heap(pPath, data_size);
657 if (static_cast<uint>(data_size) != data_size)
660 if (!data.try_resize(static_cast<uint>(data_size)))
663 if (!data.grant_ownership(static_cast<uint8 *>(p), static_cast<uint>(data_size), static_cast<uint>(data_size)))
672 void *file_utils::read_file_to_heap(const char *pPath, size_t &data_size)
676 FILE *pFile = vogl_fopen(pPath, "rb");
680 vogl_fseek(pFile, 0, SEEK_END);
681 uint64_t file_size = vogl_ftell(pFile);
682 vogl_fseek(pFile, 0, SEEK_SET);
684 if (file_size > VOGL_MAX_POSSIBLE_HEAP_BLOCK_SIZE)
690 data_size = static_cast<size_t>(file_size);
691 VOGL_ASSERT(data_size == file_size);
693 void *p = vogl_malloc(data_size);
701 bool success = (vogl_fread(p, 1, data_size, pFile) == data_size);
715 bool file_utils::read_proc_file(const char *filename, vogl::growable_array<char, 2048> &data)
718 uint file_length = 0;
719 int nbytes = data.cMaxFixedElements;
721 int fd = open(filename, O_RDONLY);
726 // Make sure we've got enough room to read in another nbytes.
727 data.resize(file_length + nbytes);
729 // Try to read in nbytes. read returns 0:end of file, -1:error.
730 ssize_t length = read(fd, data.get_ptr() + file_length, nbytes);
734 file_length += length;
735 if (length != nbytes)
745 // Trim trailing whitespace.
746 while ((file_length > 0) && vogl::vogl_isspace(data[file_length - 1]))
749 data.resize(file_length + 1);
750 data[file_length] = 0;
754 bool file_utils::write_buf_to_file(const char *pPath, const void *pData, size_t data_size)
756 FILE *pFile = vogl_fopen(pPath, "wb");
760 bool success = vogl_fwrite(pData, 1, data_size, pFile) == data_size;
762 if (vogl_fclose(pFile) == EOF)
768 bool file_utils::write_vec_to_file(const char *pPath, const uint8_vec &data)
770 return write_buf_to_file(pPath, data.get_ptr(), data.size());
773 // This helper only works on files with valid file sizes (i.e. it won't work on some files under proc such as /proc/self/status).
774 bool file_utils::read_text_file(const char *pPath, dynamic_string_array &lines, uint flags)
777 if (!stream.open(pPath, cDataStreamReadable))
779 if (flags & cRTFPrintErrorMessages)
780 console::error("%s: Failed opening text file \"%s\" for reading\n", VOGL_FUNCTION_NAME, pPath);
781 else if (flags & cRTFPrintWarningMessages)
782 console::warning("%s: Failed opening text file \"%s\" for reading\n", VOGL_FUNCTION_NAME, pPath);
787 dynamic_string line_str;
788 while (stream.get_remaining())
790 if (!stream.read_line(line_str))
792 if (flags & cRTFPrintErrorMessages)
793 console::error("%s: Failed reading from text file \"%s\"\n", VOGL_FUNCTION_NAME, pPath);
794 else if (flags & cRTFPrintWarningMessages)
795 console::warning("%s: Failed reading from text file \"%s\"\n", VOGL_FUNCTION_NAME, pPath);
800 if (flags & cRTFTrim)
803 if (flags & cRTFTrimEnd)
806 if (flags & cRTFIgnoreEmptyLines)
808 if (line_str.is_empty())
812 if ((flags & cRTFIgnoreCommentedLines) && (line_str.get_len() >= 2))
814 bool found_comment = false;
816 for (int i = 0; i < static_cast<int>(line_str.get_len()); ++i)
818 char c = line_str[i];
820 if ((c == ' ') || (c == '\t'))
822 else if ((c == '/') && (line_str[i + 1] == '/'))
823 found_comment = true;
832 lines.push_back(line_str);
838 bool file_utils::read_text_file_crt(const char *pPath, dynamic_string_array &lines)
840 FILE *pFile = fopen(pPath, "r");
848 fgets(buf, sizeof(buf), pFile);
849 lines.push_back(buf);
857 bool file_utils::write_text_file(const char *pPath, const dynamic_string_array &lines, bool unix_line_endings)
860 if (!stream.open(pPath, cDataStreamWritable))
863 for (uint i = 0; i < lines.size(); i++)
865 const dynamic_string &str = lines[i];
867 stream.write(str.get_ptr(), str.get_len());
869 if (unix_line_endings)
870 stream.write("\n", 1);
872 stream.write("\r\n", 2);
875 return !stream.get_error();
878 bool file_utils::write_string_to_file(const char *pPath, const dynamic_string &str)
881 if (!stream.open(pPath, cDataStreamWritable))
885 stream.write(str.get_ptr(), str.get_len());
887 return !stream.get_error();
890 dynamic_string file_utils::generate_temp_filename(const char *pPath)
896 rm.seed_from_urandom();
898 return dynamic_string(cVarArg, "%s__temp_%016" PRIX64 "%016" PRIX64 "__", pPath, rm.urand64(), rm.urand64());
901 bool file_utils::change_directory(const char *pPath)
903 return chdir(pPath) == 0;
906 // TODO: Windows version
907 char *file_utils::get_exec_filename(char *pPath, size_t dest_len)
909 ssize_t s = readlink("/proc/self/exe", pPath, dest_len);