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
22IOHANDLE 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
82unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
83{
84 return fread(ptr: buffer, size: 1, n: size, stream: (FILE *)io);
85}
86
87bool 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
144char *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
160int io_skip(IOHANDLE io, int64_t size)
161{
162 return io_seek(io, offset: size, origin: IOSEEK_CUR);
163}
164
165int 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
189int64_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
198int64_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
212unsigned io_write(IOHANDLE io, const void *buffer, unsigned size)
213{
214 return fwrite(ptr: buffer, size: 1, n: size, s: (FILE *)io);
215}
216
217bool 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
226int io_close(IOHANDLE io)
227{
228 return fclose(stream: (FILE *)io) != 0;
229}
230
231int io_flush(IOHANDLE io)
232{
233 return fflush(stream: (FILE *)io);
234}
235
236int 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
249int io_error(IOHANDLE io)
250{
251 return ferror(stream: (FILE *)io);
252}
253
254IOHANDLE io_stdin()
255{
256 return stdin;
257}
258
259IOHANDLE io_stdout()
260{
261 return stdout;
262}
263
264IOHANDLE io_stderr()
265{
266 return stderr;
267}
268
269IOHANDLE 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