src/fileio.cpp
changeset 8088 7c1a4dd586ae
parent 8086 e4d64a7ab363
child 8120 2ab4ad82fd44
equal deleted inserted replaced
8087:064305edb998 8088:7c1a4dd586ae
   202 	"data" PATHSEP,
   202 	"data" PATHSEP,
   203 	"lang" PATHSEP
   203 	"lang" PATHSEP
   204 };
   204 };
   205 
   205 
   206 const char *_searchpaths[NUM_SEARCHPATHS];
   206 const char *_searchpaths[NUM_SEARCHPATHS];
   207 std::vector<const char *> _tar_list;
   207 TarList _tar_list;
       
   208 TarFileList _tar_filelist;
   208 
   209 
   209 /**
   210 /**
   210  * Check whether the given file exists
   211  * Check whether the given file exists
   211  * @param filename the file to try for existance
   212  * @param filename the file to try for existance
   212  * @param subdir the subdirectory to look in
   213  * @param subdir the subdirectory to look in
   215 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
   216 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
   216 {
   217 {
   217 	FILE *f = FioFOpenFile(filename, "rb", subdir);
   218 	FILE *f = FioFOpenFile(filename, "rb", subdir);
   218 	if (f == NULL) return false;
   219 	if (f == NULL) return false;
   219 
   220 
       
   221 	FioFCloseFile(f);
       
   222 	return true;
       
   223 }
       
   224 
       
   225 /**
       
   226  * Close a file in a safe way.
       
   227  */
       
   228 void FioFCloseFile(FILE *f)
       
   229 {
   220 	fclose(f);
   230 	fclose(f);
   221 	return true;
       
   222 }
   231 }
   223 
   232 
   224 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
   233 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
   225 {
   234 {
   226 	assert(subdir < NUM_SUBDIRS);
   235 	assert(subdir < NUM_SUBDIRS);
   301 		fseek(f, 0, SEEK_SET);
   310 		fseek(f, 0, SEEK_SET);
   302 	}
   311 	}
   303 	return f;
   312 	return f;
   304 }
   313 }
   305 
   314 
   306 FILE *FioTarFileList(const char *tar, const char *mode, size_t *filesize, FioTarFileListCallback *callback, void *userdata)
   315 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
       
   316 {
       
   317 	FILE *f = fopen(entry->tar->filename, "rb");
       
   318 	assert(f != NULL);
       
   319 
       
   320 	fseek(f, entry->position, SEEK_SET);
       
   321 	if (filesize != NULL) *filesize = entry->size;
       
   322 	return f;
       
   323 }
       
   324 
       
   325 /** Opens OpenTTD files somewhere in a personal or global directory */
       
   326 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
       
   327 {
       
   328 	FILE *f = NULL;
       
   329 	Searchpath sp;
       
   330 
       
   331 	assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
       
   332 
       
   333 	FOR_ALL_SEARCHPATHS(sp) {
       
   334 		f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
       
   335 		if (f != NULL || subdir == NO_DIRECTORY) break;
       
   336 	}
       
   337 
       
   338 	/* We can only use .tar in case of data-dir, and read-mode */
       
   339 	if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') {
       
   340 		/* Filenames in tars are always forced to be lowercase */
       
   341 		char *lcfilename = strdup(filename);
       
   342 		strtolower(lcfilename);
       
   343 		TarFileList::iterator it = _tar_filelist.find(lcfilename);
       
   344 		free(lcfilename);
       
   345 		if (it != _tar_filelist.end()) {
       
   346 			f = FioFOpenFileTar(&((*it).second), filesize);
       
   347 		}
       
   348 	}
       
   349 
       
   350 	return f;
       
   351 }
       
   352 
       
   353 /**
       
   354  * Create a directory with the given name
       
   355  * @param name the new name of the directory
       
   356  */
       
   357 void FioCreateDirectory(const char *name)
       
   358 {
       
   359 #if defined(WIN32) || defined(WINCE)
       
   360 	CreateDirectory(OTTD2FS(name), NULL);
       
   361 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
       
   362 	mkdir(OTTD2FS(name));
       
   363 #else
       
   364 	mkdir(OTTD2FS(name), 0755);
       
   365 #endif
       
   366 }
       
   367 
       
   368 /**
       
   369  * Appends, if necessary, the path separator character to the end of the string.
       
   370  * It does not add the path separator to zero-sized strings.
       
   371  * @param buf    string to append the separator to
       
   372  * @param buflen the length of the buf
       
   373  */
       
   374 void AppendPathSeparator(char *buf, size_t buflen)
       
   375 {
       
   376 	size_t s = strlen(buf);
       
   377 
       
   378 	/* Length of string + path separator + '\0' */
       
   379 	if (s != 0 && buf[s - 1] != PATHSEPCHAR && s + 2 < buflen) {
       
   380 		buf[s]     = PATHSEPCHAR;
       
   381 		buf[s + 1] = '\0';
       
   382 	}
       
   383 }
       
   384 
       
   385 /**
       
   386  * Allocates and files a variable with the full path
       
   387  * based on the given directory.
       
   388  * @param dir the directory to base the path on
       
   389  * @return the malloced full path
       
   390  */
       
   391 char *BuildWithFullPath(const char *dir)
       
   392 {
       
   393 	char *dest = MallocT<char>(MAX_PATH);
       
   394 	ttd_strlcpy(dest, dir, MAX_PATH);
       
   395 
       
   396 	/* Check if absolute or relative path */
       
   397 	const char *s = strchr(dest, PATHSEPCHAR);
       
   398 
       
   399 	/* Add absolute path */
       
   400 	if (s == NULL || dest != s) {
       
   401 		getcwd(dest, MAX_PATH);
       
   402 		AppendPathSeparator(dest, MAX_PATH);
       
   403 		ttd_strlcat(dest, dir, MAX_PATH);
       
   404 	}
       
   405 	AppendPathSeparator(dest, MAX_PATH);
       
   406 
       
   407 	return dest;
       
   408 }
       
   409 
       
   410 static bool TarListAddFile(const char *filename)
   307 {
   411 {
   308 	/* The TAR-header, repeated for every file */
   412 	/* The TAR-header, repeated for every file */
   309 	typedef struct TarHeader {
   413 	typedef struct TarHeader {
   310 		char name[100];      ///< Name of the file
   414 		char name[100];      ///< Name of the file
   311 		char mode[8];
   415 		char mode[8];
   325 		char prefix[155];    ///< Path of the file
   429 		char prefix[155];    ///< Path of the file
   326 
   430 
   327 		char unused[12];
   431 		char unused[12];
   328 	} TarHeader;
   432 	} TarHeader;
   329 
   433 
   330 	assert(mode[0] == 'r'); // Only reading is supported
   434 	/* Check if we already seen this file */
   331 	assert(callback != NULL); // We need a callback, else this function doens't do much
   435 	TarList::iterator it = _tar_list.find(filename);
   332 
   436 	if (it != _tar_list.end()) return false;
   333 #if defined(WIN32) && defined(UNICODE)
   437 
   334 	/* fopen is implemented as a define with ellipses for
   438 	FILE *f = fopen(filename, "rb");
   335 	 * Unicode support (prepend an L). As we are not sending
       
   336 	 * a string, but a variable, it 'renames' the variable,
       
   337 	 * so make that variable to makes it compile happily */
       
   338 	wchar_t Lmode[5];
       
   339 	MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
       
   340 #endif
       
   341 
       
   342 	FILE *f = fopen(tar, mode);
       
   343 	assert(f != NULL);
   439 	assert(f != NULL);
       
   440 
       
   441 	TarListEntry *tar_entry = MallocT<TarListEntry>(1);
       
   442 	tar_entry->filename = strdup(filename);
       
   443 	_tar_list.insert(TarList::value_type(filename, tar_entry));
   344 
   444 
   345 	TarHeader th;
   445 	TarHeader th;
   346 	char buf[sizeof(th.name) + 1], *end;
   446 	char buf[sizeof(th.name) + 1], *end;
   347 	char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
   447 	char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
       
   448 	int num = 0, pos = 0;
       
   449 
       
   450 	/* Make a char of 512 empty bytes */
       
   451 	char empty[512];
       
   452 	memset(&empty[0], 0, sizeof(empty));
   348 
   453 
   349 	while (!feof(f)) {
   454 	while (!feof(f)) {
   350 		/* Read the header and make sure it is a valid one */
       
   351 		fread(&th, 1, 512, f);
   455 		fread(&th, 1, 512, f);
   352 		/* 'ustar' is the new format, '\0' is the old format */
   456 		pos += 512;
   353 		if (th.magic[0] != '\0' && strncmp(th.magic, "ustar", 5) != 0) return NULL;
   457 
       
   458 		/* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
       
   459 		if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
       
   460 			/* If we have only zeros in the block, it can be an end-of-file indicator */
       
   461 			if (memcmp(&th, &empty[0], 512) == 0) continue;
       
   462 
       
   463 			DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
       
   464 			return false;
       
   465 		}
   354 
   466 
   355 		name[0] = '\0';
   467 		name[0] = '\0';
   356 		int len = 0;
   468 		int len = 0;
   357 
   469 
   358 		/* The prefix contains the directory-name */
   470 		/* The prefix contains the directory-name */
   374 		int skip = strtol(buf, &end, 8);
   486 		int skip = strtol(buf, &end, 8);
   375 
   487 
   376 		/* 0 byte sized files can be skipped (dirs, symlinks, ..) */
   488 		/* 0 byte sized files can be skipped (dirs, symlinks, ..) */
   377 		if (skip == 0) continue;
   489 		if (skip == 0) continue;
   378 
   490 
   379 		/* Check in the callback if this is the file we want */
   491 		/* Store this entry in the list */
   380 		if (callback(name, skip, userdata)) {
   492 		TarFileListEntry entry;
   381 			if (filesize != NULL) *filesize = skip;
   493 		entry.tar      = tar_entry;
   382 			return f;
   494 		entry.size     = skip;
   383 		}
   495 		entry.position = pos;
       
   496 		/* Force lowercase */
       
   497 		strtolower(name);
       
   498 
       
   499 		/* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */
       
   500 #if (PATHSEPCHAR != '/')
       
   501 		for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
       
   502 #endif
       
   503 
       
   504 		DEBUG(misc, 6, "Found file in tar: %s (%d bytes, %d offset)", name, skip, pos);
       
   505 		if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++;
   384 
   506 
   385 		/* Skip to the next block.. */
   507 		/* Skip to the next block.. */
   386 		fseek(f, ALIGN(skip, 512), SEEK_CUR);
   508 		skip = ALIGN(skip, 512);
   387 	}
   509 		fseek(f, skip, SEEK_CUR);
   388 
   510 		pos += skip;
       
   511 	}
       
   512 
       
   513 	DEBUG(misc, 1, "Found tar '%s' with %d new files", filename, num);
   389 	fclose(f);
   514 	fclose(f);
   390 	return NULL;
       
   391 }
       
   392 
       
   393 bool FioFOpenFileTarFileListCallback(const char *filename, int size, void *search_filename)
       
   394 {
       
   395 	return strcasecmp(filename, (const char *)search_filename) == 0;
       
   396 }
       
   397 
       
   398 FILE *FioFOpenFileTar(const char *filename, const char *tar_filename, size_t *filesize)
       
   399 {
       
   400 	return FioTarFileList(tar_filename, "rb", filesize, FioFOpenFileTarFileListCallback, (void *)filename);
       
   401 }
       
   402 
       
   403 /** Opens OpenTTD files somewhere in a personal or global directory */
       
   404 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
       
   405 {
       
   406 	FILE *f = NULL;
       
   407 	Searchpath sp;
       
   408 
       
   409 	assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
       
   410 
       
   411 	FOR_ALL_SEARCHPATHS(sp) {
       
   412 		f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
       
   413 		if (f != NULL || subdir == NO_DIRECTORY) break;
       
   414 	}
       
   415 	/* We can only use .tar in case of data-dir, and read-mode */
       
   416 	if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') {
       
   417 		const char *tar;
       
   418 		FOR_ALL_TARS(tar) {
       
   419 			f = FioFOpenFileTar(filename, tar, filesize);
       
   420 			if (f != NULL) break;
       
   421 		}
       
   422 	}
       
   423 
       
   424 	return f;
       
   425 }
       
   426 
       
   427 /**
       
   428  * Create a directory with the given name
       
   429  * @param name the new name of the directory
       
   430  */
       
   431 void FioCreateDirectory(const char *name)
       
   432 {
       
   433 #if defined(WIN32) || defined(WINCE)
       
   434 	CreateDirectory(OTTD2FS(name), NULL);
       
   435 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
       
   436 	mkdir(OTTD2FS(name));
       
   437 #else
       
   438 	mkdir(OTTD2FS(name), 0755);
       
   439 #endif
       
   440 }
       
   441 
       
   442 /**
       
   443  * Appends, if necessary, the path separator character to the end of the string.
       
   444  * It does not add the path separator to zero-sized strings.
       
   445  * @param buf    string to append the separator to
       
   446  * @param buflen the length of the buf
       
   447  */
       
   448 void AppendPathSeparator(char *buf, size_t buflen)
       
   449 {
       
   450 	size_t s = strlen(buf);
       
   451 
       
   452 	/* Length of string + path separator + '\0' */
       
   453 	if (s != 0 && buf[s - 1] != PATHSEPCHAR && s + 2 < buflen) {
       
   454 		buf[s]     = PATHSEPCHAR;
       
   455 		buf[s + 1] = '\0';
       
   456 	}
       
   457 }
       
   458 
       
   459 /**
       
   460  * Allocates and files a variable with the full path
       
   461  * based on the given directory.
       
   462  * @param dir the directory to base the path on
       
   463  * @return the malloced full path
       
   464  */
       
   465 char *BuildWithFullPath(const char *dir)
       
   466 {
       
   467 	char *dest = MallocT<char>(MAX_PATH);
       
   468 	ttd_strlcpy(dest, dir, MAX_PATH);
       
   469 
       
   470 	/* Check if absolute or relative path */
       
   471 	const char *s = strchr(dest, PATHSEPCHAR);
       
   472 
       
   473 	/* Add absolute path */
       
   474 	if (s == NULL || dest != s) {
       
   475 		getcwd(dest, MAX_PATH);
       
   476 		AppendPathSeparator(dest, MAX_PATH);
       
   477 		ttd_strlcat(dest, dir, MAX_PATH);
       
   478 	}
       
   479 	AppendPathSeparator(dest, MAX_PATH);
       
   480 
       
   481 	return dest;
       
   482 }
       
   483 
       
   484 static bool TarListAddFile(const char *filename)
       
   485 {
       
   486 	/* See if we already have a tar by that name; useless to have double entries in our list */
       
   487 	const char *tar;
       
   488 	FOR_ALL_TARS(tar) {
       
   489 		if (strcmp(tar, filename) == 0) return false;
       
   490 	}
       
   491 
       
   492 	DEBUG(misc, 1, "Found tar: %s", filename);
       
   493 	_tar_list.push_back(strdup(filename));
       
   494 
   515 
   495 	return true;
   516 	return true;
   496 }
   517 }
   497 
   518 
   498 static int ScanPathForTarFiles(const char *path, int basepath_length)
   519 static int ScanPathForTarFiles(const char *path, int basepath_length)