1/*
2 * Posix-esque support routines for PhysicsFS.
3 *
4 * Please see the file LICENSE.txt in the source's root directory.
5 *
6 * This file written by Ryan C. Gordon.
7 */
8
9#define __PHYSICSFS_INTERNAL__
10#include "physfs_platforms.h"
11
12#ifdef PHYSFS_PLATFORM_POSIX
13
14#include <unistd.h>
15#include <ctype.h>
16#include <sys/types.h>
17#include <sys/stat.h>
18#include <pwd.h>
19#include <dirent.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <pthread.h>
23
24#include "physfs_internal.h"
25
26
27static PHYSFS_ErrorCode errcodeFromErrnoError(const int err)
28{
29 switch (err)
30 {
31 case 0: return PHYSFS_ERR_OK;
32 case EACCES: return PHYSFS_ERR_PERMISSION;
33 case EPERM: return PHYSFS_ERR_PERMISSION;
34 case EDQUOT: return PHYSFS_ERR_NO_SPACE;
35 case EIO: return PHYSFS_ERR_IO;
36 case ELOOP: return PHYSFS_ERR_SYMLINK_LOOP;
37 case EMLINK: return PHYSFS_ERR_NO_SPACE;
38 case ENAMETOOLONG: return PHYSFS_ERR_BAD_FILENAME;
39 case ENOENT: return PHYSFS_ERR_NOT_FOUND;
40 case ENOSPC: return PHYSFS_ERR_NO_SPACE;
41 case ENOTDIR: return PHYSFS_ERR_NOT_FOUND;
42 case EISDIR: return PHYSFS_ERR_NOT_A_FILE;
43 case EROFS: return PHYSFS_ERR_READ_ONLY;
44 case ETXTBSY: return PHYSFS_ERR_BUSY;
45 case EBUSY: return PHYSFS_ERR_BUSY;
46 case ENOMEM: return PHYSFS_ERR_OUT_OF_MEMORY;
47 case ENOTEMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY;
48 default: return PHYSFS_ERR_OS_ERROR;
49 } /* switch */
50} /* errcodeFromErrnoError */
51
52
53static inline PHYSFS_ErrorCode errcodeFromErrno(void)
54{
55 return errcodeFromErrnoError(errno);
56} /* errcodeFromErrno */
57
58
59static char *getUserDirByUID(void)
60{
61 uid_t uid = getuid();
62 struct passwd *pw;
63 char *retval = NULL;
64
65 pw = getpwuid(uid);
66 if ((pw != NULL) && (pw->pw_dir != NULL) && (*pw->pw_dir != '\0'))
67 {
68 const size_t dlen = strlen(pw->pw_dir);
69 const size_t add_dirsep = (pw->pw_dir[dlen-1] != '/') ? 1 : 0;
70 retval = (char *) allocator.Malloc(dlen + 1 + add_dirsep);
71 if (retval != NULL)
72 {
73 strcpy(retval, pw->pw_dir);
74 if (add_dirsep)
75 {
76 retval[dlen] = '/';
77 retval[dlen+1] = '\0';
78 } /* if */
79 } /* if */
80 } /* if */
81
82 return retval;
83} /* getUserDirByUID */
84
85
86char *__PHYSFS_platformCalcUserDir(void)
87{
88 char *retval = NULL;
89 char *envr = getenv("HOME");
90
91 /* if the environment variable was set, make sure it's really a dir. */
92 if (envr != NULL)
93 {
94 struct stat statbuf;
95 if ((stat(envr, &statbuf) != -1) && (S_ISDIR(statbuf.st_mode)))
96 {
97 const size_t envrlen = strlen(envr);
98 const size_t add_dirsep = (envr[envrlen-1] != '/') ? 1 : 0;
99 retval = allocator.Malloc(envrlen + 1 + add_dirsep);
100 if (retval)
101 {
102 strcpy(retval, envr);
103 if (add_dirsep)
104 {
105 retval[envrlen] = '/';
106 retval[envrlen+1] = '\0';
107 } /* if */
108 } /* if */
109 } /* if */
110 } /* if */
111
112 if (retval == NULL)
113 retval = getUserDirByUID();
114
115 return retval;
116} /* __PHYSFS_platformCalcUserDir */
117
118
119PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
120 PHYSFS_EnumerateCallback callback,
121 const char *origdir, void *callbackdata)
122{
123 DIR *dir;
124 struct dirent *ent;
125 PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
126
127 dir = opendir(dirname);
128 BAIL_IF(dir == NULL, errcodeFromErrno(), PHYSFS_ENUM_ERROR);
129
130 while ((retval == PHYSFS_ENUM_OK) && ((ent = readdir(dir)) != NULL))
131 {
132 const char *name = ent->d_name;
133 if (name[0] == '.') /* ignore "." and ".." */
134 {
135 if ((name[1] == '\0') || ((name[1] == '.') && (name[2] == '\0')))
136 continue;
137 } /* if */
138
139 retval = callback(callbackdata, origdir, name);
140 if (retval == PHYSFS_ENUM_ERROR)
141 PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
142 } /* while */
143
144 closedir(dir);
145
146 return retval;
147} /* __PHYSFS_platformEnumerate */
148
149
150int __PHYSFS_platformMkDir(const char *path)
151{
152 const int rc = mkdir(path, S_IRWXU);
153 BAIL_IF(rc == -1, errcodeFromErrno(), 0);
154 return 1;
155} /* __PHYSFS_platformMkDir */
156
157
158#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
159static inline void set_CLOEXEC(int fildes)
160{
161 int flags = fcntl(fildes, F_GETFD);
162 if (flags != -1) {
163 fcntl(fildes, F_SETFD, flags | FD_CLOEXEC);
164 }
165}
166#endif
167
168static void *doOpen(const char *filename, int mode)
169{
170 const int appending = (mode & O_APPEND);
171 int fd;
172 int *retval;
173
174 errno = 0;
175
176 /* O_APPEND doesn't actually behave as we'd like. */
177 mode &= ~O_APPEND;
178
179#ifdef O_CLOEXEC
180 /* Add O_CLOEXEC if defined */
181 mode |= O_CLOEXEC;
182#endif
183
184 do {
185 fd = open(filename, mode, S_IRUSR | S_IWUSR);
186 } while ((fd < 0) && (errno == EINTR));
187 BAIL_IF(fd < 0, errcodeFromErrno(), NULL);
188
189#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
190 set_CLOEXEC(fd);
191#endif
192
193 if (appending)
194 {
195 if (lseek(fd, 0, SEEK_END) < 0)
196 {
197 const int err = errno;
198 close(fd);
199 BAIL(errcodeFromErrnoError(err), NULL);
200 } /* if */
201 } /* if */
202
203 retval = (int *) allocator.Malloc(sizeof (int));
204 if (!retval)
205 {
206 close(fd);
207 BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
208 } /* if */
209
210 *retval = fd;
211 return ((void *) retval);
212} /* doOpen */
213
214
215void *__PHYSFS_platformOpenRead(const char *filename)
216{
217 return doOpen(filename, O_RDONLY);
218} /* __PHYSFS_platformOpenRead */
219
220
221void *__PHYSFS_platformOpenWrite(const char *filename)
222{
223 return doOpen(filename, O_WRONLY | O_CREAT | O_TRUNC);
224} /* __PHYSFS_platformOpenWrite */
225
226
227void *__PHYSFS_platformOpenAppend(const char *filename)
228{
229 return doOpen(filename, O_WRONLY | O_CREAT | O_APPEND);
230} /* __PHYSFS_platformOpenAppend */
231
232
233PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer,
234 PHYSFS_uint64 len)
235{
236 const int fd = *((int *) opaque);
237 ssize_t rc = 0;
238
239 if (!__PHYSFS_ui64FitsAddressSpace(len))
240 BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
241
242 do {
243 rc = read(fd, buffer, (size_t) len);
244 } while ((rc == -1) && (errno == EINTR));
245 BAIL_IF(rc == -1, errcodeFromErrno(), -1);
246 assert(rc >= 0);
247 assert(rc <= len);
248 return (PHYSFS_sint64) rc;
249} /* __PHYSFS_platformRead */
250
251
252PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
253 PHYSFS_uint64 len)
254{
255 const int fd = *((int *) opaque);
256 ssize_t rc = 0;
257
258 if (!__PHYSFS_ui64FitsAddressSpace(len))
259 BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
260
261 do {
262 rc = write(fd, (void *) buffer, (size_t) len);
263 } while ((rc == -1) && (errno == EINTR));
264 BAIL_IF(rc == -1, errcodeFromErrno(), rc);
265 assert(rc >= 0);
266 assert(rc <= len);
267 return (PHYSFS_sint64) rc;
268} /* __PHYSFS_platformWrite */
269
270
271int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
272{
273 const int fd = *((int *) opaque);
274 const off_t rc = lseek(fd, (off_t) pos, SEEK_SET);
275 BAIL_IF(rc == -1, errcodeFromErrno(), 0);
276 return 1;
277} /* __PHYSFS_platformSeek */
278
279
280PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
281{
282 const int fd = *((int *) opaque);
283 PHYSFS_sint64 retval;
284 retval = (PHYSFS_sint64) lseek(fd, 0, SEEK_CUR);
285 BAIL_IF(retval == -1, errcodeFromErrno(), -1);
286 return retval;
287} /* __PHYSFS_platformTell */
288
289
290PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
291{
292 const int fd = *((int *) opaque);
293 struct stat statbuf;
294 BAIL_IF(fstat(fd, &statbuf) == -1, errcodeFromErrno(), -1);
295 return ((PHYSFS_sint64) statbuf.st_size);
296} /* __PHYSFS_platformFileLength */
297
298
299int __PHYSFS_platformFlush(void *opaque)
300{
301 const int fd = *((int *) opaque);
302 int rc = -1;
303 if ((fcntl(fd, F_GETFL) & O_ACCMODE) != O_RDONLY) {
304 do {
305 rc = fsync(fd);
306 } while ((rc == -1) && (errno == EINTR));
307 BAIL_IF(rc == -1, errcodeFromErrno(), 0);
308 }
309 return 1;
310} /* __PHYSFS_platformFlush */
311
312
313void __PHYSFS_platformClose(void *opaque)
314{
315 const int fd = *((int *) opaque);
316 int rc = -1;
317 do {
318 rc = close(fd); /* we don't check this. You should have used flush! */
319 } while ((rc == -1) && (errno == EINTR));
320 allocator.Free(opaque);
321} /* __PHYSFS_platformClose */
322
323
324int __PHYSFS_platformDelete(const char *path)
325{
326 BAIL_IF(remove(path) == -1, errcodeFromErrno(), 0);
327 return 1;
328} /* __PHYSFS_platformDelete */
329
330
331int __PHYSFS_platformStat(const char *fname, PHYSFS_Stat *st, const int follow)
332{
333 struct stat statbuf;
334 const int rc = follow ? stat(fname, &statbuf) : lstat(fname, &statbuf);
335 BAIL_IF(rc == -1, errcodeFromErrno(), 0);
336
337 if (S_ISREG(statbuf.st_mode))
338 {
339 st->filetype = PHYSFS_FILETYPE_REGULAR;
340 st->filesize = statbuf.st_size;
341 } /* if */
342
343 else if(S_ISDIR(statbuf.st_mode))
344 {
345 st->filetype = PHYSFS_FILETYPE_DIRECTORY;
346 st->filesize = 0;
347 } /* else if */
348
349 else if(S_ISLNK(statbuf.st_mode))
350 {
351 st->filetype = PHYSFS_FILETYPE_SYMLINK;
352 st->filesize = 0;
353 } /* else if */
354
355 else
356 {
357 st->filetype = PHYSFS_FILETYPE_OTHER;
358 st->filesize = statbuf.st_size;
359 } /* else */
360
361 st->modtime = statbuf.st_mtime;
362 st->createtime = statbuf.st_ctime;
363 st->accesstime = statbuf.st_atime;
364
365 st->readonly = (access(fname, W_OK) == -1);
366 return 1;
367} /* __PHYSFS_platformStat */
368
369
370typedef struct
371{
372 pthread_mutex_t mutex;
373 pthread_t owner;
374 PHYSFS_uint32 count;
375} PthreadMutex;
376
377
378void *__PHYSFS_platformGetThreadID(void)
379{
380 return ( (void *) ((size_t) pthread_self()) );
381} /* __PHYSFS_platformGetThreadID */
382
383
384void *__PHYSFS_platformCreateMutex(void)
385{
386 int rc;
387 PthreadMutex *m = (PthreadMutex *) allocator.Malloc(sizeof (PthreadMutex));
388 BAIL_IF(!m, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
389 rc = pthread_mutex_init(&m->mutex, NULL);
390 if (rc != 0)
391 {
392 allocator.Free(m);
393 BAIL(PHYSFS_ERR_OS_ERROR, NULL);
394 } /* if */
395
396 m->count = 0;
397 m->owner = (pthread_t) 0xDEADBEEF;
398 return ((void *) m);
399} /* __PHYSFS_platformCreateMutex */
400
401
402void __PHYSFS_platformDestroyMutex(void *mutex)
403{
404 PthreadMutex *m = (PthreadMutex *) mutex;
405
406 /* Destroying a locked mutex is a bug, but we'll try to be helpful. */
407 if ((m->owner == pthread_self()) && (m->count > 0))
408 pthread_mutex_unlock(&m->mutex);
409
410 pthread_mutex_destroy(&m->mutex);
411 allocator.Free(m);
412} /* __PHYSFS_platformDestroyMutex */
413
414
415int __PHYSFS_platformGrabMutex(void *mutex)
416{
417 PthreadMutex *m = (PthreadMutex *) mutex;
418 pthread_t tid = pthread_self();
419 if (m->owner != tid)
420 {
421 if (pthread_mutex_lock(&m->mutex) != 0)
422 return 0;
423 m->owner = tid;
424 } /* if */
425
426 m->count++;
427 return 1;
428} /* __PHYSFS_platformGrabMutex */
429
430
431void __PHYSFS_platformReleaseMutex(void *mutex)
432{
433 PthreadMutex *m = (PthreadMutex *) mutex;
434 assert(m->owner == pthread_self()); /* catch programming errors. */
435 assert(m->count > 0); /* catch programming errors. */
436 if (m->owner == pthread_self())
437 {
438 if (--m->count == 0)
439 {
440 m->owner = (pthread_t) 0xDEADBEEF;
441 pthread_mutex_unlock(&m->mutex);
442 } /* if */
443 } /* if */
444} /* __PHYSFS_platformReleaseMutex */
445
446#endif /* PHYSFS_PLATFORM_POSIX */
447
448/* end of physfs_platform_posix.c ... */
449
450