x86UNIXFileio.cc 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. /* JMQ:
  23. Here's the scoop on unix file IO. The windows platform makes some
  24. assumptions about fileio: 1) the file system is case-insensitive, and
  25. 2) the platform can write to the directory in which
  26. the game is running. Both of these are usually false on linux. So, to
  27. compensate, we "route" created files and directories to the user's home
  28. directory (see GetPrefPath()). When a file is to be accessed, the code
  29. looks in the home directory first. If the file is not found there and the
  30. open mode is read only, the code will look in the game installation
  31. directory. Files are never created or modified in the game directory.
  32. For case-sensitivity, the MungePath code will test whether a given path
  33. specified by the engine exists. If not, it will use the MungeCase function
  34. which will try to determine if an actual filesystem path matches the
  35. specified path case insensitive. If one is found, the actual path
  36. transparently (we hope) replaces the one requested by the engine.
  37. The preference directory is global to all torque executables with the same
  38. name. You should make sure you keep it clean if you build from multiple
  39. torque development trees.
  40. */
  41. #include "platformX86UNIX/x86UNIXState.h"
  42. // evil hack to get around insane X windows #define-happy header files
  43. #ifdef Status
  44. #undef Status
  45. #endif
  46. #include "platformX86UNIX/platformX86UNIX.h"
  47. #include "platform/platformFileIO.h"
  48. #include "collection/vector.h"
  49. #include "string/stringTable.h"
  50. #include "console/console.h"
  51. #include "io/resource/resourceManager.h"
  52. #include "game/gameInterface.h"
  53. #if defined(__FreeBSD__)
  54. #include <sys/types.h>
  55. #endif
  56. #include <utime.h>
  57. /* include sys/param.h for MAXPATHLEN */
  58. #include <sys/param.h>
  59. #ifndef MAX_PATH
  60. #define MAX_PATH MAXPATHLEN
  61. #endif
  62. /* these are for reading directors, getting stats, etc. */
  63. #include <dirent.h>
  64. #include <sys/types.h>
  65. #include <sys/stat.h>
  66. #include <unistd.h>
  67. #include <fcntl.h>
  68. #include <errno.h>
  69. #include <stdlib.h>
  70. extern int x86UNIXOpen(const char *path, int oflag);
  71. extern int x86UNIXClose(int fd);
  72. extern ssize_t x86UNIXRead(int fd, void *buf, size_t nbytes);
  73. extern ssize_t x86UNIXWrite(int fd, const void *buf, size_t nbytes);
  74. const int MaxPath = 2048;
  75. // Various handy utility functions:
  76. //------------------------------------------------------------------------------
  77. // find all \ in a path and convert them in place to /
  78. static void ForwardSlash(char *str)
  79. {
  80. while(*str)
  81. {
  82. if(*str == '\\')
  83. *str = '/';
  84. str++;
  85. }
  86. }
  87. //------------------------------------------------------------------------------
  88. // copy a file from src to dest
  89. static bool CopyFile(const char* src, const char* dest)
  90. {
  91. S32 srcFd = x86UNIXOpen(src, O_RDONLY);
  92. S32 destFd = x86UNIXOpen(dest, O_WRONLY | O_CREAT | O_TRUNC);
  93. bool error = false;
  94. if (srcFd != -1 && destFd != -1)
  95. {
  96. const int BufSize = 8192;
  97. char buf[BufSize];
  98. S32 bytesRead = 0;
  99. while ((bytesRead = x86UNIXRead(srcFd, buf, BufSize)) > 0)
  100. {
  101. // write data
  102. if (x86UNIXWrite(destFd, buf, bytesRead) == -1)
  103. {
  104. error = true;
  105. break;
  106. }
  107. }
  108. if (bytesRead == -1)
  109. error = true;
  110. }
  111. if (srcFd != -1)
  112. x86UNIXClose(srcFd);
  113. if (destFd != -1)
  114. x86UNIXClose(destFd);
  115. if (error)
  116. {
  117. Con::errorf("Error copying file: %s, %s", src, dest);
  118. remove(dest);
  119. }
  120. return error;
  121. }
  122. bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite)
  123. {
  124. return CopyFile(fromName,toName);
  125. }
  126. //-----------------------------------------------------------------------------
  127. static char sgPrefDir[MaxPath];
  128. static bool sgPrefDirInitialized = false;
  129. // get the "pref dir", which is where game output files are stored. the pref
  130. // dir is ~/PREF_DIR_ROOT/PREF_DIR_GAME_NAME
  131. static const char* GetPrefDir()
  132. {
  133. if (sgPrefDirInitialized)
  134. return sgPrefDir;
  135. if (x86UNIXState->getUseRedirect())
  136. {
  137. const char *home = getenv("HOME");
  138. AssertFatal(home, "HOME environment variable must be set");
  139. dSprintf(sgPrefDir, MaxPath, "%s/%s/%s",
  140. home, PREF_DIR_ROOT, PREF_DIR_GAME_NAME);
  141. }
  142. else
  143. {
  144. getcwd(sgPrefDir, MaxPath);
  145. }
  146. sgPrefDirInitialized = true;
  147. return sgPrefDir;
  148. }
  149. //------------------------------------------------------------------------------
  150. // munge the case of the specified pathName. This means try to find the actual
  151. // filename in with case-insensitive matching on the specified pathName, and
  152. // store the actual found name.
  153. static void MungeCase(char* pathName, S32 pathNameSize)
  154. {
  155. char tempBuf[MaxPath];
  156. dStrncpy(tempBuf, pathName, pathNameSize);
  157. AssertFatal(pathName[0] == '/', "PATH must be absolute");
  158. struct stat filestat;
  159. const int MaxPathEl = 200;
  160. char *currChar = pathName;
  161. char testPath[MaxPath];
  162. char pathEl[MaxPathEl];
  163. bool done = false;
  164. dStrncpy(tempBuf, "/", MaxPath);
  165. currChar++;
  166. while (!done)
  167. {
  168. char* termChar = dStrchr(currChar, '/');
  169. if (termChar == NULL)
  170. termChar = dStrchr(currChar, '\0');
  171. AssertFatal(termChar, "Can't find / or NULL terminator");
  172. S32 pathElLen = (termChar - currChar);
  173. dStrncpy(pathEl, currChar, pathElLen);
  174. pathEl[pathElLen] = '\0';
  175. dStrncpy(testPath, tempBuf, MaxPath);
  176. dStrcat(testPath, pathEl);
  177. if (stat(testPath, &filestat) != -1)
  178. {
  179. dStrncpy(tempBuf, testPath, MaxPath);
  180. }
  181. else
  182. {
  183. DIR *dir = opendir(tempBuf);
  184. struct dirent* ent;
  185. bool foundMatch = false;
  186. while (dir != NULL && (ent = readdir(dir)) != NULL)
  187. {
  188. if (dStricmp(pathEl, ent->d_name) == 0)
  189. {
  190. foundMatch = true;
  191. dStrcat(tempBuf, ent->d_name);
  192. break;
  193. }
  194. }
  195. if (!foundMatch)
  196. dStrncpy(tempBuf, testPath, MaxPath);
  197. if (dir)
  198. closedir(dir);
  199. }
  200. if (*termChar == '/')
  201. {
  202. dStrcat(tempBuf, "/");
  203. termChar++;
  204. currChar = termChar;
  205. }
  206. else
  207. done = true;
  208. }
  209. dStrncpy(pathName, tempBuf, pathNameSize);
  210. }
  211. //-----------------------------------------------------------------------------
  212. // Returns true if the pathname exists, false otherwise. If isFile is true,
  213. // the pathname is assumed to be a file path, and only the directory part
  214. // will be examined (everything before last /)
  215. bool DirExists(char* pathname, bool isFile)
  216. {
  217. static char testpath[20000];
  218. dStrncpy(testpath, pathname, sizeof(testpath));
  219. if (isFile)
  220. {
  221. // find the last / and make it into null
  222. char* lastSlash = dStrrchr(testpath, '/');
  223. if (lastSlash != NULL)
  224. *lastSlash = 0;
  225. }
  226. return Platform::isDirectory(testpath);
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Munge the specified path.
  230. static void MungePath(char* dest, S32 destSize,
  231. const char* src, const char* absolutePrefix)
  232. {
  233. char tempBuf[MaxPath];
  234. dStrncpy(dest, src, MaxPath);
  235. // translate all \ to /
  236. ForwardSlash(dest);
  237. // if it is relative, make it absolute with the absolutePrefix
  238. if (dest[0] != '/')
  239. {
  240. AssertFatal(absolutePrefix, "Absolute Prefix must not be NULL");
  241. dSprintf(tempBuf, MaxPath, "%s/%s",
  242. absolutePrefix, dest);
  243. // copy the result back into dest
  244. dStrncpy(dest, tempBuf, destSize);
  245. }
  246. // if the path exists, we're done
  247. struct stat filestat;
  248. if (stat(dest, &filestat) != -1)
  249. return;
  250. // otherwise munge the case of the path
  251. MungeCase(dest, destSize);
  252. }
  253. //-----------------------------------------------------------------------------
  254. enum
  255. {
  256. TOUCH,
  257. DELETE
  258. };
  259. //-----------------------------------------------------------------------------
  260. // perform a modification on the specified file. allowed modifications are
  261. // specified in the enum above.
  262. bool ModifyFile(const char * name, S32 modType)
  263. {
  264. if(!name || (dStrlen(name) >= MAX_PATH) || dStrstr(name, "../") != NULL)
  265. return(false);
  266. // if its absolute skip it
  267. if (name[0]=='/' || name[0]=='\\')
  268. return(false);
  269. // only modify files in home directory
  270. char prefPathName[MaxPath];
  271. MungePath(prefPathName, MaxPath, name, GetPrefDir());
  272. if (modType == TOUCH)
  273. return(utime(prefPathName, 0) != -1);
  274. else if (modType == DELETE)
  275. return (remove(prefPathName) != -1);
  276. else
  277. AssertFatal(false, "Unknown File Mod type");
  278. return false;
  279. }
  280. //-----------------------------------------------------------------------------
  281. static bool RecurseDumpPath(const char *path, const char* relativePath, const char *pattern, Vector<Platform::FileInfo> &fileVector, int recurseDepth)
  282. {
  283. char search[1024];
  284. dSprintf(search, sizeof(search), "%s", path, pattern);
  285. DIR *directory = opendir(search);
  286. if (directory == NULL)
  287. return false;
  288. struct dirent *fEntry;
  289. fEntry = readdir(directory); // read the first "file" in the directory
  290. if (fEntry == NULL)
  291. return false;
  292. do
  293. {
  294. char filename[BUFSIZ+1];
  295. struct stat fStat;
  296. dSprintf(filename, sizeof(filename), "%s/%s", search, fEntry->d_name); // "construct" the file name
  297. stat(filename, &fStat); // get the file stats
  298. if ( (fStat.st_mode & S_IFMT) == S_IFDIR )
  299. {
  300. // Directory
  301. // skip . and .. directories
  302. if (dStrcmp(fEntry->d_name, ".") == 0 || dStrcmp(fEntry->d_name, "..") == 0)
  303. continue;
  304. // skip excluded directories
  305. if( Platform::isExcludedDirectory(fEntry->d_name))
  306. continue;
  307. char child[MaxPath];
  308. dSprintf(child, sizeof(child), "%s/%s", path, fEntry->d_name);
  309. char* childRelative = NULL;
  310. char childRelativeBuf[MaxPath];
  311. if (relativePath)
  312. {
  313. dSprintf(childRelativeBuf, sizeof(childRelativeBuf), "%s/%s",
  314. relativePath, fEntry->d_name);
  315. childRelative = childRelativeBuf;
  316. }
  317. if (recurseDepth > 0)
  318. RecurseDumpPath(child, childRelative, pattern, fileVector, recurseDepth - 1);
  319. else if (recurseDepth == -1)
  320. RecurseDumpPath(child, childRelative, pattern, fileVector, -1);
  321. }
  322. else
  323. {
  324. // File
  325. // add it to the list
  326. fileVector.increment();
  327. Platform::FileInfo& rInfo = fileVector.last();
  328. if (relativePath)
  329. rInfo.pFullPath = StringTable->insert(relativePath);
  330. else
  331. rInfo.pFullPath = StringTable->insert(path);
  332. rInfo.pFileName = StringTable->insert(fEntry->d_name);
  333. rInfo.fileSize = fStat.st_size;
  334. //dPrintf("Adding file: %s/%s\n", rInfo.pFullPath, rInfo.pFileName);
  335. }
  336. } while( (fEntry = readdir(directory)) != NULL );
  337. closedir(directory);
  338. return true;
  339. }
  340. //-----------------------------------------------------------------------------
  341. bool dFileDelete(const char * name)
  342. {
  343. return ModifyFile(name, DELETE);
  344. }
  345. //-----------------------------------------------------------------------------
  346. bool dFileTouch(const char * name)
  347. {
  348. return ModifyFile(name, TOUCH);
  349. }
  350. //-----------------------------------------------------------------------------
  351. // Constructors & Destructor
  352. //-----------------------------------------------------------------------------
  353. //-----------------------------------------------------------------------------
  354. // After construction, the currentStatus will be Closed and the capabilities
  355. // will be 0.
  356. //-----------------------------------------------------------------------------
  357. File::File()
  358. : currentStatus(Closed), capability(0)
  359. {
  360. // AssertFatal(sizeof(int) == sizeof(void *), "File::File: cannot cast void* to int");
  361. handle = (void *)NULL;
  362. }
  363. //-----------------------------------------------------------------------------
  364. // insert a copy constructor here... (currently disabled)
  365. //-----------------------------------------------------------------------------
  366. //-----------------------------------------------------------------------------
  367. // Destructor
  368. //-----------------------------------------------------------------------------
  369. File::~File()
  370. {
  371. close();
  372. handle = (void *)NULL;
  373. }
  374. //-----------------------------------------------------------------------------
  375. // Open a file in the mode specified by openMode (Read, Write, or ReadWrite).
  376. // Truncate the file if the mode is either Write or ReadWrite and truncate is
  377. // true.
  378. //
  379. // Sets capability appropriate to the openMode.
  380. // Returns the currentStatus of the file.
  381. //-----------------------------------------------------------------------------
  382. File::Status File::open(const char *filename, const AccessMode openMode)
  383. {
  384. AssertFatal(NULL != filename, "File::open: NULL filename");
  385. AssertWarn(NULL == handle, "File::open: handle already valid");
  386. // Close the file if it was already open...
  387. if (Closed != currentStatus)
  388. close();
  389. char prefPathName[MaxPath];
  390. char gamePathName[MaxPath];
  391. char cwd[MaxPath];
  392. getcwd(cwd, MaxPath);
  393. MungePath(prefPathName, MaxPath, filename, GetPrefDir());
  394. MungePath(gamePathName, MaxPath, filename, cwd);
  395. int oflag;
  396. struct stat filestat;
  397. handle = (void *)dRealMalloc(sizeof(int));
  398. switch (openMode)
  399. {
  400. case Read:
  401. oflag = O_RDONLY;
  402. break;
  403. case Write:
  404. oflag = O_WRONLY | O_CREAT | O_TRUNC;
  405. break;
  406. case ReadWrite:
  407. oflag = O_RDWR | O_CREAT;
  408. // if the file does not exist copy it before reading/writing
  409. if (stat(prefPathName, &filestat) == -1)
  410. bool ret = CopyFile(gamePathName, prefPathName);
  411. break;
  412. case WriteAppend:
  413. oflag = O_WRONLY | O_CREAT | O_APPEND;
  414. // if the file does not exist copy it before appending
  415. if (stat(prefPathName, &filestat) == -1)
  416. bool ret = CopyFile(gamePathName, prefPathName);
  417. break;
  418. default:
  419. AssertFatal(false, "File::open: bad access mode"); // impossible
  420. }
  421. // if we are writing, make sure output path exists
  422. if (openMode == Write || openMode == ReadWrite || openMode == WriteAppend)
  423. Platform::createPath(prefPathName);
  424. int fd = -1;
  425. fd = x86UNIXOpen(prefPathName, oflag);
  426. if (fd == -1 && openMode == Read)
  427. // for read only files we can use the gamePathName
  428. fd = x86UNIXOpen(gamePathName, oflag);
  429. dMemcpy(handle, &fd, sizeof(int));
  430. #ifdef DEBUG
  431. // fprintf(stdout,"fd = %d, handle = %d\n", fd, *((int *)handle));
  432. #endif
  433. if (*((int *)handle) == -1)
  434. {
  435. // handle not created successfully
  436. Con::errorf("Can't open file: %s", filename);
  437. return setStatus();
  438. }
  439. else
  440. {
  441. // successfully created file, so set the file capabilities...
  442. switch (openMode)
  443. {
  444. case Read:
  445. capability = U32(FileRead);
  446. break;
  447. case Write:
  448. case WriteAppend:
  449. capability = U32(FileWrite);
  450. break;
  451. case ReadWrite:
  452. capability = U32(FileRead) |
  453. U32(FileWrite);
  454. break;
  455. default:
  456. AssertFatal(false, "File::open: bad access mode");
  457. }
  458. return currentStatus = Ok; // success!
  459. }
  460. }
  461. //-----------------------------------------------------------------------------
  462. // Get the current position of the file pointer.
  463. //-----------------------------------------------------------------------------
  464. U32 File::getPosition() const
  465. {
  466. AssertFatal(Closed != currentStatus, "File::getPosition: file closed");
  467. AssertFatal(NULL != handle, "File::getPosition: invalid file handle");
  468. #ifdef DEBUG
  469. // fprintf(stdout, "handle = %d\n",*((int *)handle));fflush(stdout);
  470. #endif
  471. return (U32) lseek(*((int *)handle), 0, SEEK_CUR);
  472. }
  473. //-----------------------------------------------------------------------------
  474. // Set the position of the file pointer.
  475. // Absolute and relative positioning is supported via the absolutePos
  476. // parameter.
  477. //
  478. // If positioning absolutely, position MUST be positive - an IOError results if
  479. // position is negative.
  480. // Position can be negative if positioning relatively, however positioning
  481. // before the start of the file is an IOError.
  482. //
  483. // Returns the currentStatus of the file.
  484. //-----------------------------------------------------------------------------
  485. File::Status File::setPosition(S32 position, bool absolutePos)
  486. {
  487. AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
  488. AssertFatal(NULL != handle, "File::setPosition: invalid file handle");
  489. if (Ok != currentStatus && EOS != currentStatus)
  490. return currentStatus;
  491. U32 finalPos = 0;
  492. switch (absolutePos)
  493. {
  494. case true: // absolute position
  495. AssertFatal(0 <= position, "File::setPosition: negative absolute position");
  496. // position beyond EOS is OK
  497. finalPos = lseek(*((int *)handle), position, SEEK_SET);
  498. break;
  499. case false: // relative position
  500. AssertFatal((getPosition() >= (U32)abs(position) && 0 > position) || 0 <= position, "File::setPosition: negative relative position");
  501. // position beyond EOS is OK
  502. finalPos = lseek(*((int *)handle), position, SEEK_CUR);
  503. break;
  504. }
  505. if (0xffffffff == finalPos)
  506. return setStatus(); // unsuccessful
  507. else if (finalPos >= getSize())
  508. return currentStatus = EOS; // success, at end of file
  509. else
  510. return currentStatus = Ok; // success!
  511. }
  512. //-----------------------------------------------------------------------------
  513. // Get the size of the file in bytes.
  514. // It is an error to query the file size for a Closed file, or for one with an
  515. // error status.
  516. //-----------------------------------------------------------------------------
  517. U32 File::getSize() const
  518. {
  519. AssertWarn(Closed != currentStatus, "File::getSize: file closed");
  520. AssertFatal(NULL != handle, "File::getSize: invalid file handle");
  521. if (Ok == currentStatus || EOS == currentStatus)
  522. {
  523. long currentOffset = getPosition(); // keep track of our current position
  524. long fileSize;
  525. lseek(*((int *)handle), 0, SEEK_END); // seek to the end of the file
  526. fileSize = getPosition(); // get the file size
  527. lseek(*((int *)handle), currentOffset, SEEK_SET); // seek back to our old offset
  528. return fileSize; // success!
  529. }
  530. else
  531. return 0; // unsuccessful
  532. }
  533. //-----------------------------------------------------------------------------
  534. // Flush the file.
  535. // It is an error to flush a read-only file.
  536. // Returns the currentStatus of the file.
  537. //-----------------------------------------------------------------------------
  538. File::Status File::flush()
  539. {
  540. AssertFatal(Closed != currentStatus, "File::flush: file closed");
  541. AssertFatal(NULL != handle, "File::flush: invalid file handle");
  542. AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
  543. if (fsync(*((int *)handle)) == 0)
  544. return currentStatus = Ok; // success!
  545. else
  546. return setStatus(); // unsuccessful
  547. }
  548. //-----------------------------------------------------------------------------
  549. // Close the File.
  550. //
  551. // Returns the currentStatus
  552. //-----------------------------------------------------------------------------
  553. File::Status File::close()
  554. {
  555. // if the handle is non-NULL, close it if necessary and free it
  556. if (NULL != handle)
  557. {
  558. // make a local copy of the handle value and
  559. // free the handle
  560. int handleVal = *((int *)handle);
  561. dRealFree(handle);
  562. handle = (void *)NULL;
  563. // close the handle if it is valid
  564. if (handleVal != -1 && x86UNIXClose(handleVal) != 0)
  565. return setStatus(); // unsuccessful
  566. }
  567. // Set the status to closed
  568. return currentStatus = Closed;
  569. }
  570. //-----------------------------------------------------------------------------
  571. // Self-explanatory.
  572. //-----------------------------------------------------------------------------
  573. File::Status File::getStatus() const
  574. {
  575. return currentStatus;
  576. }
  577. //-----------------------------------------------------------------------------
  578. // Sets and returns the currentStatus when an error has been encountered.
  579. //-----------------------------------------------------------------------------
  580. File::Status File::setStatus()
  581. {
  582. Con::printf("File IO error: %s", strerror(errno));
  583. return currentStatus = IOError;
  584. }
  585. //-----------------------------------------------------------------------------
  586. // Sets and returns the currentStatus to status.
  587. //-----------------------------------------------------------------------------
  588. File::Status File::setStatus(File::Status status)
  589. {
  590. return currentStatus = status;
  591. }
  592. //-----------------------------------------------------------------------------
  593. // Read from a file.
  594. // The number of bytes to read is passed in size, the data is returned in src.
  595. // The number of bytes read is available in bytesRead if a non-Null pointer is
  596. // provided.
  597. //-----------------------------------------------------------------------------
  598. File::Status File::read(U32 size, char *dst, U32 *bytesRead)
  599. {
  600. #ifdef DEBUG
  601. // fprintf(stdout,"reading %d bytes\n",size);fflush(stdout);
  602. #endif
  603. AssertFatal(Closed != currentStatus, "File::read: file closed");
  604. AssertFatal(NULL != handle, "File::read: invalid file handle");
  605. AssertFatal(NULL != dst, "File::read: NULL destination pointer");
  606. AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
  607. AssertWarn(0 != size, "File::read: size of zero");
  608. /* show stats for this file */
  609. #ifdef DEBUG
  610. //struct stat st;
  611. //fstat(*((int *)handle), &st);
  612. //fprintf(stdout,"file size = %d\n", st.st_size);
  613. #endif
  614. /****************************/
  615. if (Ok != currentStatus || 0 == size)
  616. return currentStatus;
  617. else
  618. {
  619. long lastBytes;
  620. long *bytes = (NULL == bytesRead) ? &lastBytes : (long *)bytesRead;
  621. if ( (*((U32 *)bytes) = x86UNIXRead(*((int *)handle), dst, size)) == -1)
  622. {
  623. #ifdef DEBUG
  624. // fprintf(stdout,"unsuccessful: %d\n", *((U32 *)bytes));fflush(stdout);
  625. #endif
  626. return setStatus(); // unsuccessful
  627. } else {
  628. // dst[*((U32 *)bytes)] = '\0';
  629. if (*((U32 *)bytes) != size || *((U32 *)bytes) == 0) {
  630. #ifdef DEBUG
  631. // fprintf(stdout,"end of stream: %d\n", *((U32 *)bytes));fflush(stdout);
  632. #endif
  633. return currentStatus = EOS; // end of stream
  634. }
  635. }
  636. }
  637. // dst[*bytesRead] = '\0';
  638. #ifdef DEBUG
  639. //fprintf(stdout, "We read:\n");
  640. //fprintf(stdout, "====================================================\n");
  641. //fprintf(stdout, "%s\n",dst);
  642. //fprintf(stdout, "====================================================\n");
  643. //fprintf(stdout,"read ok: %d\n", *bytesRead);fflush(stdout);
  644. #endif
  645. return currentStatus = Ok; // successfully read size bytes
  646. }
  647. //-----------------------------------------------------------------------------
  648. // Write to a file.
  649. // The number of bytes to write is passed in size, the data is passed in src.
  650. // The number of bytes written is available in bytesWritten if a non-Null
  651. // pointer is provided.
  652. //-----------------------------------------------------------------------------
  653. File::Status File::write(U32 size, const char *src, U32 *bytesWritten)
  654. {
  655. // JMQ: despite the U32 parameters, the maximum filesize supported by this
  656. // function is probably the max value of S32, due to the unix syscall
  657. // api.
  658. AssertFatal(Closed != currentStatus, "File::write: file closed");
  659. AssertFatal(NULL != handle, "File::write: invalid file handle");
  660. AssertFatal(NULL != src, "File::write: NULL source pointer");
  661. AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability");
  662. AssertWarn(0 != size, "File::write: size of zero");
  663. if ((Ok != currentStatus && EOS != currentStatus) || 0 == size)
  664. return currentStatus;
  665. else
  666. {
  667. S32 numWritten = x86UNIXWrite(*((int *)handle), src, size);
  668. if (numWritten < 0)
  669. return setStatus();
  670. if (bytesWritten)
  671. *bytesWritten = static_cast<U32>(numWritten);
  672. return currentStatus = Ok;
  673. }
  674. }
  675. //-----------------------------------------------------------------------------
  676. // Self-explanatory. JMQ: No explanation needed. Move along. These aren't
  677. // the droids you're looking for.
  678. //-----------------------------------------------------------------------------
  679. bool File::hasCapability(Capability cap) const
  680. {
  681. return (0 != (U32(cap) & capability));
  682. }
  683. //-----------------------------------------------------------------------------
  684. S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b)
  685. {
  686. if(a > b)
  687. return 1;
  688. if(a < b)
  689. return -1;
  690. return 0;
  691. }
  692. //-----------------------------------------------------------------------------
  693. static bool GetFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime)
  694. {
  695. struct stat fStat;
  696. if (stat(filePath, &fStat) == -1)
  697. return false;
  698. if(createTime)
  699. {
  700. // no where does SysV/BSD UNIX keep a record of a file's
  701. // creation time. instead of creation time I'll just use
  702. // changed time for now.
  703. *createTime = fStat.st_ctime;
  704. }
  705. if(modifyTime)
  706. {
  707. *modifyTime = fStat.st_mtime;
  708. }
  709. return true;
  710. }
  711. //-----------------------------------------------------------------------------
  712. bool Platform::getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime)
  713. {
  714. char pathName[MaxPath];
  715. // if it starts with cwd, we need to strip that off so that we can look for
  716. // the file in the pref dir
  717. char cwd[MaxPath];
  718. getcwd(cwd, MaxPath);
  719. if (dStrstr(filePath, cwd) == filePath)
  720. filePath = filePath + dStrlen(cwd) + 1;
  721. // if its relative, first look in the pref dir
  722. if (filePath[0] != '/' && filePath[0] != '\\')
  723. {
  724. MungePath(pathName, MaxPath, filePath, GetPrefDir());
  725. if (GetFileTimes(pathName, createTime, modifyTime))
  726. return true;
  727. }
  728. // here if the path is absolute or not in the pref dir
  729. MungePath(pathName, MaxPath, filePath, cwd);
  730. return GetFileTimes(pathName, createTime, modifyTime);
  731. }
  732. //-----------------------------------------------------------------------------
  733. bool Platform::createPath(const char *file)
  734. {
  735. char pathbuf[MaxPath];
  736. const char *dir;
  737. pathbuf[0] = 0;
  738. U32 pathLen = 0;
  739. // all paths should be created in home directory
  740. char prefPathName[MaxPath];
  741. MungePath(prefPathName, MaxPath, file, GetPrefDir());
  742. file = prefPathName;
  743. // does the directory exist already?
  744. if (DirExists(prefPathName, true)) // true means that the path is a filepath
  745. return true;
  746. while((dir = dStrchr(file, '/')) != NULL)
  747. {
  748. dStrncpy(pathbuf + pathLen, file, dir - file);
  749. pathbuf[pathLen + dir-file] = 0;
  750. bool ret = mkdir(pathbuf, 0700);
  751. pathLen += dir - file;
  752. pathbuf[pathLen++] = '/';
  753. file = dir + 1;
  754. }
  755. return true;
  756. }
  757. // JMQ: Platform:cdFileExists in unimplemented
  758. //------------------------------------------------------------------------------
  759. // bool Platform::cdFileExists(const char *filePath, const char *volumeName,
  760. // S32 serialNum)
  761. // {
  762. // }
  763. //-----------------------------------------------------------------------------
  764. bool Platform::dumpPath(const char *path, Vector<Platform::FileInfo> &fileVector, int depth)
  765. {
  766. const char* pattern = "*";
  767. // if it is not absolute, dump the pref dir first
  768. if (path[0] != '/' && path[0] != '\\')
  769. {
  770. char prefPathName[MaxPath];
  771. MungePath(prefPathName, MaxPath, path, GetPrefDir());
  772. RecurseDumpPath(prefPathName, path, pattern, fileVector, depth);
  773. }
  774. // munge the requested path and dump it
  775. char mungedPath[MaxPath];
  776. char cwd[MaxPath];
  777. getcwd(cwd, MaxPath);
  778. MungePath(mungedPath, MaxPath, path, cwd);
  779. return RecurseDumpPath(mungedPath, path, pattern, fileVector, depth);
  780. }
  781. //-----------------------------------------------------------------------------
  782. bool Platform::isFile(const char *pFilePath)
  783. {
  784. if (!pFilePath || !*pFilePath)
  785. return false;
  786. // Get file info
  787. struct stat fStat;
  788. if (stat(pFilePath, &fStat) < 0)
  789. return false;
  790. // if the file is a "regular file" then true
  791. if ( (fStat.st_mode & S_IFMT) == S_IFREG)
  792. return true;
  793. // must be some other file (directory, device, etc.)
  794. return false;
  795. }
  796. //-----------------------------------------------------------------------------
  797. S32 Platform::getFileSize(const char *pFilePath)
  798. {
  799. if (!pFilePath || !*pFilePath)
  800. return -1;
  801. // Get the file info
  802. struct stat fStat;
  803. if (stat(pFilePath, &fStat) < 0)
  804. return -1;
  805. // if the file is a "regular file" then return the size
  806. if ( (fStat.st_mode & S_IFMT) == S_IFREG)
  807. return fStat.st_size;
  808. // Must be something else or we can't read the file.
  809. return -1;
  810. }
  811. //-----------------------------------------------------------------------------
  812. bool Platform::isDirectory(const char *pDirPath)
  813. {
  814. if (!pDirPath || !*pDirPath)
  815. return false;
  816. // Get file info
  817. struct stat fStat;
  818. if (stat(pDirPath, &fStat) < 0)
  819. return false;
  820. // if the file is a Directory then true
  821. if ( (fStat.st_mode & S_IFMT) == S_IFDIR)
  822. return true;
  823. return false;
  824. }
  825. //-----------------------------------------------------------------------------
  826. bool Platform::isSubDirectory(const char *pParent, const char *pDir)
  827. {
  828. if (!pParent || !*pDir)
  829. return false;
  830. // this is somewhat of a brute force method but we need to be 100% sure
  831. // that the user cannot enter things like ../dir or /dir etc,...
  832. DIR *directory;
  833. directory = opendir(pParent);
  834. if (directory == NULL)
  835. return false;
  836. struct dirent *fEntry;
  837. fEntry = readdir(directory);
  838. if ( fEntry == NULL )
  839. return false;
  840. do
  841. {
  842. char dirBuf[MAXPATHLEN];
  843. struct stat fStat;
  844. dSprintf(dirBuf, sizeof(dirBuf), "%s/%s", pParent, fEntry->d_name);
  845. if (stat(dirBuf, &fStat) < 0)
  846. continue;
  847. // if it is a directory...
  848. if ( (fStat.st_mode & S_IFMT) == S_IFDIR)
  849. {
  850. // and the names match
  851. if (dStrcmp(pDir, fEntry->d_name ) == 0)
  852. {
  853. // then we have a real sub directory
  854. closedir(directory);
  855. return true;
  856. }
  857. }
  858. } while( (fEntry = readdir(directory)) != NULL );
  859. closedir(directory);
  860. return false;
  861. }
  862. //-----------------------------------------------------------------------------
  863. bool Platform::hasSubDirectory(const char *pPath)
  864. {
  865. if (!pPath)
  866. return false;
  867. ResourceManager->initExcludedDirectories();
  868. struct dirent *d;
  869. DIR *dip;
  870. dip = opendir(pPath);
  871. if (dip == NULL)
  872. return false;
  873. while (d = readdir(dip))
  874. {
  875. bool isDir = false;
  876. if (d->d_type == DT_UNKNOWN)
  877. {
  878. char child [1024];
  879. if ((pPath[dStrlen(pPath) - 1] == '/'))
  880. dSprintf(child, 1024, "%s%s", pPath, d->d_name);
  881. else
  882. dSprintf(child, 1024, "%s/%s", pPath, d->d_name);
  883. isDir = Platform::isDirectory (child);
  884. }
  885. else if (d->d_type & DT_DIR)
  886. isDir = true;
  887. if( isDir )
  888. {
  889. // Skip the . and .. directories
  890. if (dStrcmp(d->d_name, ".") == 0 ||dStrcmp(d->d_name, "..") == 0)
  891. continue;
  892. if (Platform::isExcludedDirectory(d->d_name))
  893. continue;
  894. Platform::clearExcludedDirectories();
  895. closedir(dip);
  896. return true;
  897. }
  898. }
  899. closedir(dip);
  900. Platform::clearExcludedDirectories();
  901. return false;
  902. }
  903. static bool recurseDumpDirectories(const char *basePath, const char *subPath, Vector<StringTableEntry> &directoryVector, S32 currentDepth, S32 recurseDepth, bool noBasePath)
  904. {
  905. // Assemble a path to open.
  906. char Path[1024];
  907. dMemset(Path, 0, 1024);
  908. // Check if subPath is not null, and not an empty string.
  909. if (subPath && (dStrncmp(subPath, "", 1) != 0)){
  910. // Concatenate it onto the basepath, accounting for if the basepath ends in a / or not
  911. if ((basePath[dStrlen(basePath) - 1]) == '/')
  912. dSprintf(Path, 1024, "%s%s", basePath, subPath);
  913. else
  914. dSprintf(Path, 1024, "%s/%s", basePath, subPath);
  915. } else {
  916. // subPath is null or an empty string.
  917. // Just look in the basePath then.
  918. dSprintf(Path, 1024, "%s", basePath);
  919. }
  920. // Open the sanitized path constructed above.
  921. DIR *dip;
  922. dip = opendir(Path);
  923. // Quit if it didn't open.
  924. if (dip == NULL)
  925. return false;
  926. // Add path to our return list (provided it is valid).
  927. if (!Platform::isExcludedDirectory(subPath)){
  928. if (noBasePath && (subPath && (dStrncmp (subPath, "", 1) != 0)) ){
  929. // There is no base path to concatenate with, and this subpath is nonempty.
  930. // Store the subPath as a starting point.
  931. directoryVector.push_back(StringTable->insert(subPath));
  932. } else {
  933. // There is a base path. Store the concatenated path.
  934. directoryVector.push_back(StringTable->insert(Path));
  935. }
  936. }
  937. // Iterate through the items in the current directory.
  938. struct dirent *d;
  939. while (d = readdir(dip)) {
  940. bool isDir;
  941. isDir = false;
  942. if (d->d_type == DT_UNKNOWN) {
  943. // If it's an unknown type, try to construct it as a path.
  944. char child [1024];
  945. // Use "/" as a separator correctly.
  946. if ((Path[dStrlen(Path) - 1] == '/'))
  947. dSprintf(child, 1024, "%s%s", Path, d->d_name);
  948. else
  949. dSprintf(child, 1024, "%s/%s", Path, d->d_name);
  950. // Now ask the platform if the constructed path is a directory.
  951. isDir = Platform::isDirectory(child);
  952. }
  953. else if (d->d_type & DT_DIR){
  954. // If it's a directory, cool.
  955. isDir = true;
  956. }
  957. // If the current directory item is itself a directory, iterate into it.
  958. if (isDir){
  959. // Bail for current directory or parent directory shorthands.
  960. if (dStrcmp(d->d_name, ".") == 0 || dStrcmp(d->d_name, "..") == 0)
  961. continue;
  962. // Bail for excluded directories.
  963. if (Platform::isExcludedDirectory(d->d_name))
  964. continue;
  965. // Now recurse into this directory.
  966. if ( (subPath && (dStrncmp(subPath, "", 1) != 0))){
  967. // The subpath we're basing on isn't an empty string.
  968. // To recurse down, cat the child directory's name with the current subpath.
  969. char child[1024];
  970. // Use "/" as a separator correctly.
  971. if ((subPath[dStrlen(subPath) - 1] == '/'))
  972. dSprintf(child, 1024, "%s%s", subPath, d->d_name);
  973. else
  974. dSprintf(child, 1024, "%s/%s", subPath, d->d_name);
  975. // If we haven't recursed too far, recurse once more with the synthesized child pathname.
  976. if (currentDepth < recurseDepth || recurseDepth == -1 ){
  977. recurseDumpDirectories(basePath, child, directoryVector, currentDepth + 1, recurseDepth, noBasePath);
  978. }
  979. } else {
  980. // The subpath we're basing on is an empty string.
  981. // Just name sure that we're not duplicating a "/" by concatenating the child directory with the base path.
  982. char child[1024];
  983. if ( (basePath[dStrlen(basePath) - 1]) == '/'){
  984. dStrcpy (child, d->d_name);
  985. }
  986. else{
  987. dSprintf(child, 1024, "/%s", d->d_name);
  988. }
  989. // If we haven't recursed too far, recurse once more with the synthesized child pathname.
  990. if (currentDepth < recurseDepth || recurseDepth == -1){
  991. recurseDumpDirectories(basePath, child, directoryVector, currentDepth + 1, recurseDepth, noBasePath);
  992. }
  993. }
  994. }
  995. }
  996. // Close this directory.
  997. closedir(dip);
  998. // End recursive calls.
  999. return true;
  1000. }
  1001. bool Platform::dumpDirectories(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
  1002. {
  1003. ResourceManager->initExcludedDirectories();
  1004. bool retVal = recurseDumpDirectories(path, "", directoryVector, 0, depth, noBasePath);
  1005. clearExcludedDirectories();
  1006. return retVal;
  1007. }
  1008. StringTableEntry Platform::getExecutableName()
  1009. {
  1010. static StringTableEntry cwd = NULL;
  1011. if (!cwd)
  1012. {
  1013. cwd = StringTable->insert(x86UNIXState->getExeName());
  1014. }
  1015. return cwd;
  1016. }
  1017. //-----------------------------------------------------------------------------
  1018. void Platform::restartInstance()
  1019. {
  1020. // execl() leaves open file descriptors open, that's the main reason it's not
  1021. // used here. We want to start fresh.
  1022. if (Game->isRunning() )
  1023. {
  1024. Con::errorf("The game is still running, we cant relaunch now!");
  1025. return;
  1026. }
  1027. char cmd[MaxPath];
  1028. sprintf(cmd, "\"%s &\"", x86UNIXState->getExePathName());
  1029. Con::printf("---- %s -----",cmd);
  1030. if(!system(cmd))
  1031. {
  1032. Con::errorf("Cannot fork new instance.");
  1033. return;
  1034. }
  1035. exit(0);
  1036. }
  1037. //-----------------------------------------------------------------------------
  1038. StringTableEntry Platform::getCurrentDirectory()
  1039. {
  1040. // get the current directory, the one that would be opened if we did a fopen(".")
  1041. char* cwd = getcwd(NULL, 0);
  1042. StringTableEntry ret = StringTable->insert(cwd);
  1043. free(cwd);
  1044. return ret;
  1045. }
  1046. //-----------------------------------------------------------------------------
  1047. StringTableEntry Platform::getExecutablePath()
  1048. {
  1049. // No obvious ways to find this with what we have
  1050. return Platform::getCurrentDirectory();
  1051. }
  1052. //-----------------------------------------------------------------------------
  1053. bool Platform::setCurrentDirectory(StringTableEntry newDir)
  1054. {
  1055. return (chdir(newDir) == 0);
  1056. }
  1057. //-----------------------------------------------------------------------------
  1058. const char* Platform::getUserDataDirectory()
  1059. {
  1060. return StringTable->insert("~/");
  1061. }
  1062. //-----------------------------------------------------------------------------
  1063. const char* Platform::getUserHomeDirectory()
  1064. {
  1065. return StringTable->insert("~/");
  1066. }
  1067. //-----------------------------------------------------------------------------
  1068. StringTableEntry Platform::osGetTemporaryDirectory()
  1069. {
  1070. return StringTable->insert("~/");
  1071. }
  1072. bool Platform::pathCopy(const char* source, const char* dest, bool nooverwrite)
  1073. {
  1074. return false;
  1075. }
  1076. bool Platform::fileRename(const char* source, const char* dest)
  1077. {
  1078. return false;
  1079. }
  1080. bool Platform::fileDelete(const char* name)
  1081. {
  1082. if (!name)
  1083. return false;
  1084. if (dStrlen(name) > MaxPath) {
  1085. Con::warnf("Platform::fileDelete() - Filename is pretty long...");
  1086. }
  1087. return (remove(name) == 0);
  1088. return false;
  1089. }
  1090. void Platform::openFolder(const char* path)
  1091. {
  1092. }