1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#if defined(SDL_FSOPS_POSIX)
25
26/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
27// System dependent filesystem routines
28
29#include "../SDL_sysfilesystem.h"
30
31#include <stdio.h>
32#include <string.h>
33#include <errno.h>
34#include <dirent.h>
35#include <sys/stat.h>
36#include <unistd.h>
37
38bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata)
39{
40 char *pathwithsep = NULL;
41 int pathwithseplen = SDL_asprintf(&pathwithsep, "%s/", path);
42 if ((pathwithseplen == -1) || (!pathwithsep)) {
43 return false;
44 }
45
46 // trim down to a single path separator at the end, in case the caller added one or more.
47 pathwithseplen--;
48 while ((pathwithseplen >= 0) && (pathwithsep[pathwithseplen] == '/')) {
49 pathwithsep[pathwithseplen--] = '\0';
50 }
51
52 DIR *dir = opendir(pathwithsep);
53 if (!dir) {
54 SDL_free(pathwithsep);
55 return SDL_SetError("Can't open directory: %s", strerror(errno));
56 }
57
58 // make sure there's a path separator at the end now for the actual callback.
59 pathwithsep[++pathwithseplen] = '/';
60 pathwithsep[++pathwithseplen] = '\0';
61
62 SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
63 struct dirent *ent;
64 while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL)) {
65 const char *name = ent->d_name;
66 if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
67 continue;
68 }
69 result = cb(userdata, pathwithsep, name);
70 }
71
72 closedir(dir);
73
74 SDL_free(pathwithsep);
75
76 return (result != SDL_ENUM_FAILURE);
77}
78
79bool SDL_SYS_RemovePath(const char *path)
80{
81 int rc = remove(path);
82 if (rc < 0) {
83 if (errno == ENOENT) {
84 // It's already gone, this is a success
85 return true;
86 }
87 return SDL_SetError("Can't remove path: %s", strerror(errno));
88 }
89 return true;
90}
91
92bool SDL_SYS_RenamePath(const char *oldpath, const char *newpath)
93{
94 if (rename(oldpath, newpath) < 0) {
95 return SDL_SetError("Can't rename path: %s", strerror(errno));
96 }
97 return true;
98}
99
100bool SDL_SYS_CopyFile(const char *oldpath, const char *newpath)
101{
102 char *buffer = NULL;
103 SDL_IOStream *input = NULL;
104 SDL_IOStream *output = NULL;
105 const size_t maxlen = 4096;
106 size_t len;
107 bool result = false;
108
109 input = SDL_IOFromFile(oldpath, "rb");
110 if (!input) {
111 goto done;
112 }
113
114 output = SDL_IOFromFile(newpath, "wb");
115 if (!output) {
116 goto done;
117 }
118
119 buffer = (char *)SDL_malloc(maxlen);
120 if (!buffer) {
121 goto done;
122 }
123
124 while ((len = SDL_ReadIO(input, buffer, maxlen)) > 0) {
125 if (SDL_WriteIO(output, buffer, len) < len) {
126 goto done;
127 }
128 }
129 if (SDL_GetIOStatus(input) != SDL_IO_STATUS_EOF) {
130 goto done;
131 }
132
133 SDL_CloseIO(input);
134 input = NULL;
135
136 if (!SDL_FlushIO(output)) {
137 goto done;
138 }
139
140 result = SDL_CloseIO(output);
141 output = NULL; // it's gone, even if it failed.
142
143done:
144 if (output) {
145 SDL_CloseIO(output);
146 }
147 if (input) {
148 SDL_CloseIO(input);
149 }
150 SDL_free(buffer);
151
152 return result;
153}
154
155bool SDL_SYS_CreateDirectory(const char *path)
156{
157 const int rc = mkdir(path, 0770);
158 if (rc < 0) {
159 const int origerrno = errno;
160 if (origerrno == EEXIST) {
161 struct stat statbuf;
162 if ((stat(path, &statbuf) == 0) && (S_ISDIR(statbuf.st_mode))) {
163 return true; // it already exists and it's a directory, consider it success.
164 }
165 }
166 return SDL_SetError("Can't create directory: %s", strerror(origerrno));
167 }
168 return true;
169}
170
171bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
172{
173 struct stat statbuf;
174 const int rc = stat(path, &statbuf);
175 if (rc < 0) {
176 return SDL_SetError("Can't stat: %s", strerror(errno));
177 } else if (S_ISREG(statbuf.st_mode)) {
178 info->type = SDL_PATHTYPE_FILE;
179 info->size = (Uint64) statbuf.st_size;
180 } else if (S_ISDIR(statbuf.st_mode)) {
181 info->type = SDL_PATHTYPE_DIRECTORY;
182 info->size = 0;
183 } else {
184 info->type = SDL_PATHTYPE_OTHER;
185 info->size = (Uint64) statbuf.st_size;
186 }
187
188#if defined(HAVE_ST_MTIM)
189 // POSIX.1-2008 standard
190 info->create_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_ctim.tv_sec) + statbuf.st_ctim.tv_nsec;
191 info->modify_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_mtim.tv_sec) + statbuf.st_mtim.tv_nsec;
192 info->access_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_atim.tv_sec) + statbuf.st_atim.tv_nsec;
193#elif defined(SDL_PLATFORM_APPLE)
194 /* Apple platform stat structs use 'st_*timespec' naming. */
195 info->create_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_ctimespec.tv_sec) + statbuf.st_ctimespec.tv_nsec;
196 info->modify_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_mtimespec.tv_sec) + statbuf.st_mtimespec.tv_nsec;
197 info->access_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_atimespec.tv_sec) + statbuf.st_atimespec.tv_nsec;
198#else
199 info->create_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_ctime);
200 info->modify_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_mtime);
201 info->access_time = (SDL_Time)SDL_SECONDS_TO_NS(statbuf.st_atime);
202#endif
203 return true;
204}
205
206// Note that this isn't actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code.
207char *SDL_SYS_GetCurrentDirectory(void)
208{
209 size_t buflen = 64;
210 char *buf = NULL;
211
212 while (true) {
213 void *ptr = SDL_realloc(buf, buflen);
214 if (!ptr) {
215 SDL_free(buf);
216 return NULL;
217 }
218 buf = (char *) ptr;
219
220 if (getcwd(buf, buflen-1) != NULL) {
221 break; // we got it!
222 }
223
224 if (errno == ERANGE) {
225 buflen *= 2; // try again with a bigger buffer.
226 continue;
227 }
228
229 SDL_free(buf);
230 SDL_SetError("getcwd failed: %s", strerror(errno));
231 return NULL;
232 }
233
234 // make sure there's a path separator at the end.
235 SDL_assert(SDL_strlen(buf) < (buflen + 2));
236 buflen = SDL_strlen(buf);
237 if ((buflen == 0) || (buf[buflen-1] != '/')) {
238 buf[buflen] = '/';
239 buf[buflen + 1] = '\0';
240 }
241
242 return buf;
243}
244
245#endif // SDL_FSOPS_POSIX
246
247