src/fileio.cpp
changeset 11139 3a72578d7811
parent 11135 6c703d2da75b
child 11140 67fe23cbc27b
--- a/src/fileio.cpp	Sun Jul 13 02:43:46 2008 +0000
+++ b/src/fileio.cpp	Sun Jul 13 16:09:00 2008 +0000
@@ -223,6 +223,9 @@
 TarList _tar_list;
 TarFileList _tar_filelist;
 
+typedef std::map<std::string, std::string> TarLinkList;
+static TarLinkList _tar_linklist; ///< List of directory links
+
 /**
  * Check whether the given file exists
  * @param filename the file to try for existance
@@ -357,11 +360,31 @@
 
 	/* We can only use .tar in case of data-dir, and read-mode */
 	if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') {
+		static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
+		char resolved_name[MAX_RESOLVED_LENGTH];
+
 		/* Filenames in tars are always forced to be lowercase */
-		char *lcfilename = strdup(filename);
-		strtolower(lcfilename);
-		TarFileList::iterator it = _tar_filelist.find(lcfilename);
-		free(lcfilename);
+		strcpy(resolved_name, filename);
+		strtolower(resolved_name);
+
+		uint resolved_len = strlen(resolved_name);
+
+		/* Resolve ONE directory link */
+		for (TarLinkList::iterator link = _tar_linklist.begin(); link != _tar_linklist.end(); link++) {
+			const std::string &src = link->first;
+			uint len = src.length();
+			if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && src.compare(0, len, resolved_name, len) == 0) {
+				/* Apply link */
+				char resolved_name2[MAX_RESOLVED_LENGTH];
+				const std::string &dest = link->second;
+				strcpy(resolved_name2, &(resolved_name[len]));
+				strcpy(resolved_name, dest.c_str());
+				strcpy(&(resolved_name[dest.length()]), resolved_name2);
+				break; // Only resolve one level
+			}
+		}
+
+		TarFileList::iterator it = _tar_filelist.find(resolved_name);
 		if (it != _tar_filelist.end()) {
 			f = FioFOpenFileTar(&((*it).second), filesize);
 		}
@@ -443,6 +466,22 @@
 	return dest;
 }
 
+/**
+ * Simplify filenames from tars.
+ * Replace '/' by PATHSEPCHAR, and force 'name' to lowercase.
+ * @param name Filename to process.
+ */
+static void SimplifyFileName(char *name)
+{
+	/* Force lowercase */
+	strtolower(name);
+
+	/* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */
+#if (PATHSEPCHAR != '/')
+	for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
+#endif
+}
+
 static bool TarListAddFile(const char *filename)
 {
 	/* The TAR-header, repeated for every file */
@@ -478,9 +517,13 @@
 	tar_entry->filename = strdup(filename);
 	_tar_list.insert(TarList::value_type(filename, tar_entry));
 
+	TarLinkList links; ///< Temporary list to collect links
+
 	TarHeader th;
 	char buf[sizeof(th.name) + 1], *end;
 	char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
+	char link[sizeof(th.linkname) + 1];
+	char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
 	size_t num = 0, pos = 0;
 
 	/* Make a char of 512 empty bytes */
@@ -522,24 +565,95 @@
 		buf[sizeof(th.size)] = '\0';
 		int skip = strtol(buf, &end, 8);
 
-		/* 0 byte sized files can be skipped (dirs, symlinks, ..) */
-		if (skip == 0) continue;
+		switch (th.typeflag) {
+			case '\0':
+			case '0': { // regular file
+				/* Ignore empty files */
+				if (skip == 0) break;
 
-		/* Store this entry in the list */
-		TarFileListEntry entry;
-		entry.tar      = tar_entry;
-		entry.size     = skip;
-		entry.position = pos;
-		/* Force lowercase */
-		strtolower(name);
+				if (strlen(name) == 0) break;
 
-		/* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */
-#if (PATHSEPCHAR != '/')
-		for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
-#endif
+				/* Store this entry in the list */
+				TarFileListEntry entry;
+				entry.tar      = tar_entry;
+				entry.size     = skip;
+				entry.position = pos;
 
-		DEBUG(misc, 6, "Found file in tar: %s (%d bytes, %d offset)", name, skip, pos);
-		if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++;
+				/* Convert to lowercase and our PATHSEPCHAR */
+				SimplifyFileName(name);
+
+				DEBUG(misc, 6, "Found file in tar: %s (%d bytes, %d offset)", name, skip, pos);
+				if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++;
+
+				break;
+			}
+
+			case '1': // hard links
+			case '2': { // symbolic links
+				/* Copy the destination of the link in a safe way at the end of 'linkname' */
+				memcpy(link, th.linkname, sizeof(th.linkname));
+				link[sizeof(th.linkname)] = '\0';
+
+				if (strlen(name) == 0 || strlen(link) == 0) break;
+
+				/* Convert to lowercase and our PATHSEPCHAR */
+				SimplifyFileName(name);
+				SimplifyFileName(link);
+
+				/* Only allow relative links */
+				if (link[0] == PATHSEPCHAR) {
+					DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
+					break;
+				}
+
+				/* Process relative path.
+				 * Note: The destination of links must not contain any directory-links. */
+				strcpy(dest, name);
+				char *destpos = strrchr(dest, PATHSEPCHAR);
+				if (destpos == NULL) destpos = dest;
+				*destpos = '\0';
+
+				char *pos = link;
+				while (*pos != '\0') {
+					char *next = strchr(link, PATHSEPCHAR);
+					if (next == NULL) next = pos + strlen(pos);
+
+					/* Skip '.' (current dir) */
+					if (next != pos + 1 || pos[0] != '.') {
+						if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
+							/* level up */
+							if (dest[0] == '\0') {
+								DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
+								break;
+							}
+
+							/* Truncate 'dest' after last PATHSEPCHAR.
+							 * This assumes, that the truncated part is a real directory and not a link */
+							destpos = strrchr(dest, PATHSEPCHAR);
+							if (destpos == NULL) destpos = dest;
+						} else {
+							/* Append at end of 'dest' */
+							if (destpos != dest) *(destpos++) = PATHSEPCHAR;
+							strncpy(destpos, pos, next - pos);
+							destpos += next - pos;
+						}
+						*destpos = '\0';
+					}
+
+					pos = next;
+				}
+
+				/* Store links in temporary list */
+				DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
+				links.insert(TarLinkList::value_type(name, dest));
+
+				break;
+			}
+
+			default:
+				/* Ignore other types */
+				break;
+		}
 
 		/* Skip to the next block.. */
 		skip = Align(skip, 512);
@@ -550,6 +664,32 @@
 	DEBUG(misc, 1, "Found tar '%s' with %d new files", filename, num);
 	fclose(f);
 
+	/* Resolve file links and store directory links.
+	 * We restrict usage of links to two cases:
+	 *  1) Links to directories:
+	 *      Both the source path and the destination path must NOT contain any further links.
+	 *      When resolving files at most one directory link is resolved.
+	 *  2) Links to files:
+	 *      The destination path must NOT contain any links.
+	 *      The source path may contain one directory link.
+	 */
+	for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
+		const std::string &src = link->first;
+		const std::string &dest = link->second;
+
+		TarFileList::iterator dest_file = _tar_filelist.find(dest);
+		if (dest_file != _tar_filelist.end()) {
+			/* Link to file. Process the link like the destination file. */
+			_tar_filelist.insert(TarFileList::value_type(src, dest_file->second));
+		} else {
+			/* Destination file not found. Assume 'link to directory' */
+			/* Append PATHSEPCHAR to 'src' and 'dest' */
+			const std::string src_path = src + PATHSEPCHAR;
+			const std::string dst_path = (dest.length() == 0 ? "" : dest + PATHSEPCHAR);
+			_tar_linklist.insert(TarLinkList::value_type(src_path, dst_path));
+		}
+	}
+
 	return true;
 }