| 1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
| 2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
| 3 | |
| 4 | #include "io.h" |
| 5 | |
| 6 | #include "dbg.h" |
| 7 | #include "detect.h" |
| 8 | #include "fs.h" |
| 9 | #include "mem.h" |
| 10 | #include "windows.h" |
| 11 | |
| 12 | #include <cstdio> |
| 13 | #include <cstdlib> |
| 14 | |
| 15 | #if defined(CONF_FAMILY_WINDOWS) |
| 16 | #include <io.h> // _get_osfhandle |
| 17 | #include <windows.h> // FlushFileBuffers |
| 18 | #else |
| 19 | #include <unistd.h> // fsync |
| 20 | #endif |
| 21 | |
| 22 | IOHANDLE io_open(const char *filename, int flags) |
| 23 | { |
| 24 | dbg_assert(flags == IOFLAG_READ || flags == IOFLAG_WRITE || flags == IOFLAG_APPEND, "flags must be read, write or append" ); |
| 25 | #if defined(CONF_FAMILY_WINDOWS) |
| 26 | const std::wstring wide_filename = windows_utf8_to_wide(filename); |
| 27 | DWORD desired_access; |
| 28 | DWORD creation_disposition; |
| 29 | const char *open_mode; |
| 30 | if((flags & IOFLAG_READ) != 0) |
| 31 | { |
| 32 | desired_access = FILE_READ_DATA; |
| 33 | creation_disposition = OPEN_EXISTING; |
| 34 | open_mode = "rb" ; |
| 35 | } |
| 36 | else if(flags == IOFLAG_WRITE) |
| 37 | { |
| 38 | desired_access = FILE_WRITE_DATA; |
| 39 | creation_disposition = CREATE_ALWAYS; |
| 40 | open_mode = "wb" ; |
| 41 | } |
| 42 | else if(flags == IOFLAG_APPEND) |
| 43 | { |
| 44 | desired_access = FILE_APPEND_DATA; |
| 45 | creation_disposition = OPEN_ALWAYS; |
| 46 | open_mode = "ab" ; |
| 47 | } |
| 48 | else |
| 49 | { |
| 50 | dbg_assert_failed("logic error" ); |
| 51 | } |
| 52 | HANDLE handle = CreateFileW(wide_filename.c_str(), desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr); |
| 53 | if(handle == INVALID_HANDLE_VALUE) |
| 54 | return nullptr; |
| 55 | const int file_descriptor = _open_osfhandle((intptr_t)handle, 0); |
| 56 | dbg_assert(file_descriptor != -1, "_open_osfhandle failure" ); |
| 57 | FILE *file_stream = _fdopen(file_descriptor, open_mode); |
| 58 | dbg_assert(file_stream != nullptr, "_fdopen failure" ); |
| 59 | return file_stream; |
| 60 | #else |
| 61 | const char *open_mode; |
| 62 | if((flags & IOFLAG_READ) != 0) |
| 63 | { |
| 64 | open_mode = "rb" ; |
| 65 | } |
| 66 | else if(flags == IOFLAG_WRITE) |
| 67 | { |
| 68 | open_mode = "wb" ; |
| 69 | } |
| 70 | else if(flags == IOFLAG_APPEND) |
| 71 | { |
| 72 | open_mode = "ab" ; |
| 73 | } |
| 74 | else |
| 75 | { |
| 76 | dbg_assert_failed("Invalid flags: %d" , flags); |
| 77 | } |
| 78 | return fopen(filename: filename, modes: open_mode); |
| 79 | #endif |
| 80 | } |
| 81 | |
| 82 | unsigned io_read(IOHANDLE io, void *buffer, unsigned size) |
| 83 | { |
| 84 | return fread(ptr: buffer, size: 1, n: size, stream: (FILE *)io); |
| 85 | } |
| 86 | |
| 87 | bool io_read_all(IOHANDLE io, void **result, unsigned *result_len) |
| 88 | { |
| 89 | // Loading files larger than 1 GiB into memory is not supported. |
| 90 | constexpr int64_t MAX_FILE_SIZE = (int64_t)1024 * 1024 * 1024; |
| 91 | |
| 92 | int64_t real_len = io_length(io); |
| 93 | if(real_len > MAX_FILE_SIZE) |
| 94 | { |
| 95 | *result = nullptr; |
| 96 | *result_len = 0; |
| 97 | return false; |
| 98 | } |
| 99 | |
| 100 | int64_t len = real_len < 0 ? 1024 : real_len; // use default initial size if we couldn't get the length |
| 101 | char *buffer = (char *)malloc(size: len + 1); |
| 102 | int64_t read = io_read(io, buffer, size: len + 1); // +1 to check if the file size is larger than expected |
| 103 | if(read < len) |
| 104 | { |
| 105 | buffer = (char *)realloc(ptr: buffer, size: read + 1); |
| 106 | len = read; |
| 107 | } |
| 108 | else if(read > len) |
| 109 | { |
| 110 | int64_t cap = 2 * read; |
| 111 | if(cap > MAX_FILE_SIZE) |
| 112 | { |
| 113 | free(ptr: buffer); |
| 114 | *result = nullptr; |
| 115 | *result_len = 0; |
| 116 | return false; |
| 117 | } |
| 118 | len = read; |
| 119 | buffer = (char *)realloc(ptr: buffer, size: cap); |
| 120 | while((read = io_read(io, buffer: buffer + len, size: cap - len)) != 0) |
| 121 | { |
| 122 | len += read; |
| 123 | if(len == cap) |
| 124 | { |
| 125 | cap *= 2; |
| 126 | if(cap > MAX_FILE_SIZE) |
| 127 | { |
| 128 | free(ptr: buffer); |
| 129 | *result = nullptr; |
| 130 | *result_len = 0; |
| 131 | return false; |
| 132 | } |
| 133 | buffer = (char *)realloc(ptr: buffer, size: cap); |
| 134 | } |
| 135 | } |
| 136 | buffer = (char *)realloc(ptr: buffer, size: len + 1); |
| 137 | } |
| 138 | buffer[len] = 0; |
| 139 | *result = buffer; |
| 140 | *result_len = len; |
| 141 | return true; |
| 142 | } |
| 143 | |
| 144 | char *io_read_all_str(IOHANDLE io) |
| 145 | { |
| 146 | void *buffer; |
| 147 | unsigned len; |
| 148 | if(!io_read_all(io, result: &buffer, result_len: &len)) |
| 149 | { |
| 150 | return nullptr; |
| 151 | } |
| 152 | if(mem_has_null(block: buffer, size: len)) |
| 153 | { |
| 154 | free(ptr: buffer); |
| 155 | return nullptr; |
| 156 | } |
| 157 | return (char *)buffer; |
| 158 | } |
| 159 | |
| 160 | int io_skip(IOHANDLE io, int64_t size) |
| 161 | { |
| 162 | return io_seek(io, offset: size, origin: IOSEEK_CUR); |
| 163 | } |
| 164 | |
| 165 | int io_seek(IOHANDLE io, int64_t offset, ESeekOrigin origin) |
| 166 | { |
| 167 | int real_origin; |
| 168 | switch(origin) |
| 169 | { |
| 170 | case IOSEEK_START: |
| 171 | real_origin = SEEK_SET; |
| 172 | break; |
| 173 | case IOSEEK_CUR: |
| 174 | real_origin = SEEK_CUR; |
| 175 | break; |
| 176 | case IOSEEK_END: |
| 177 | real_origin = SEEK_END; |
| 178 | break; |
| 179 | default: |
| 180 | dbg_assert_failed("Invalid origin: %d" , origin); |
| 181 | } |
| 182 | #if defined(CONF_FAMILY_WINDOWS) |
| 183 | return _fseeki64((FILE *)io, offset, real_origin); |
| 184 | #else |
| 185 | return fseeko(stream: (FILE *)io, off: offset, whence: real_origin); |
| 186 | #endif |
| 187 | } |
| 188 | |
| 189 | int64_t io_tell(IOHANDLE io) |
| 190 | { |
| 191 | #if defined(CONF_FAMILY_WINDOWS) |
| 192 | return _ftelli64((FILE *)io); |
| 193 | #else |
| 194 | return ftello(stream: (FILE *)io); |
| 195 | #endif |
| 196 | } |
| 197 | |
| 198 | int64_t io_length(IOHANDLE io) |
| 199 | { |
| 200 | if(io_seek(io, offset: 0, origin: IOSEEK_END) != 0) |
| 201 | { |
| 202 | return -1; |
| 203 | } |
| 204 | const int64_t length = io_tell(io); |
| 205 | if(io_seek(io, offset: 0, origin: IOSEEK_START) != 0) |
| 206 | { |
| 207 | return -1; |
| 208 | } |
| 209 | return length; |
| 210 | } |
| 211 | |
| 212 | unsigned io_write(IOHANDLE io, const void *buffer, unsigned size) |
| 213 | { |
| 214 | return fwrite(ptr: buffer, size: 1, n: size, s: (FILE *)io); |
| 215 | } |
| 216 | |
| 217 | bool io_write_newline(IOHANDLE io) |
| 218 | { |
| 219 | #if defined(CONF_FAMILY_WINDOWS) |
| 220 | return io_write(io, "\r\n" , 2) == 2; |
| 221 | #else |
| 222 | return io_write(io, buffer: "\n" , size: 1) == 1; |
| 223 | #endif |
| 224 | } |
| 225 | |
| 226 | int io_close(IOHANDLE io) |
| 227 | { |
| 228 | return fclose(stream: (FILE *)io) != 0; |
| 229 | } |
| 230 | |
| 231 | int io_flush(IOHANDLE io) |
| 232 | { |
| 233 | return fflush(stream: (FILE *)io); |
| 234 | } |
| 235 | |
| 236 | int io_sync(IOHANDLE io) |
| 237 | { |
| 238 | if(io_flush(io)) |
| 239 | { |
| 240 | return 1; |
| 241 | } |
| 242 | #if defined(CONF_FAMILY_WINDOWS) |
| 243 | return FlushFileBuffers((HANDLE)_get_osfhandle(_fileno((FILE *)io))) == FALSE; |
| 244 | #else |
| 245 | return fsync(fd: fileno(stream: (FILE *)io)) != 0; |
| 246 | #endif |
| 247 | } |
| 248 | |
| 249 | int io_error(IOHANDLE io) |
| 250 | { |
| 251 | return ferror(stream: (FILE *)io); |
| 252 | } |
| 253 | |
| 254 | IOHANDLE io_stdin() |
| 255 | { |
| 256 | return stdin; |
| 257 | } |
| 258 | |
| 259 | IOHANDLE io_stdout() |
| 260 | { |
| 261 | return stdout; |
| 262 | } |
| 263 | |
| 264 | IOHANDLE io_stderr() |
| 265 | { |
| 266 | return stderr; |
| 267 | } |
| 268 | |
| 269 | IOHANDLE io_current_exe() |
| 270 | { |
| 271 | char path[IO_MAX_PATH_LENGTH]; |
| 272 | if(fs_executable_path(buffer: path, buffer_size: sizeof(path)) != 0) |
| 273 | { |
| 274 | return nullptr; |
| 275 | } |
| 276 | return io_open(filename: path, flags: IOFLAG_READ); |
| 277 | } |
| 278 | |