/** * Opens a file from inside a tar archive. * @param entry The entry to open. * @param filesize [out] If not \c NULL, size of the opened file. * @return File handle of the opened file, or \c NULL if the file is not available. * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file. */ public static FileStream FioFOpenFileTar(TarFileListEntry entry, out long filesize) { filesize = 0; try { var f = new FileStream(entry.tar_filename, FileMode.Open); if (f.Seek(entry.position, SeekOrigin.Begin) < 0) { f.Close(); return(null); } filesize = entry.size; return(f); } catch (IOException e) { Log.Error(e); return(null); } }
public override bool AddFile(string filename, string tar_filename = null) { /* No tar within tar. */ Debug.Assert(tar_filename == null); /* The TAR-header, repeated for every file */ /* Check if we already seen this file */ if (FileIO._tar_list[(int)this.subdir].TryGetValue(filename, out var it) == false) { return(false); } var links = new Dictionary <string, string>(); try { using (var f = new FileInfo(filename).OpenRead()) { FileIO._tar_list[(int)this.subdir][filename].filename = filename; FileIO._tar_list[(int)this.subdir][filename].dirname = null; var dupped_filename = filename; /// Temporary list to collect links string name = null; //char[sizeof(th.prefix) + 1 + sizeof(th.name) + 1]; string link = null; //char link[sizeof(th.linkname) + 1]; string dest = null; //char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1]; var num = 0; var pos = 0; var buffer = new byte[TarHeader.HeaderSize]; for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it? var num_bytes_read = f.Read(buffer, 1, TarHeader.HeaderSize); if (num_bytes_read != TarHeader.HeaderSize) { break; } pos += num_bytes_read; var th = new TarHeader(buffer); /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */ if (th.magic != "ustar" && th.magic != "") { /* If we have only zeros in the block, it can be an end-of-file indicator */ if (buffer.Any(b => b != 0)) { continue; } Log.Debug($"The file '{filename}' isn't a valid tar-file"); f.Close(); return(false); } name = null; /* The prefix contains the directory-name */ if (th.prefix != "") { name = th.prefix + Path.PathSeparator; } /* Copy the name of the file in a safe way at the end of 'name' */ name += th.name; switch (th.typeflag) { case '\0': case '0': { // regular file /* Ignore empty files */ if (th.size == 0) { break; } if (name.Length == 0) { break; } /* Store this entry in the list */ var entry = new TarFileListEntry() { tar_filename = dupped_filename, size = th.size, position = pos }; /* Convert to lowercase and our PATHSEPCHAR */ name = FileIO.SimplifyFileName(name); Log.Debug($"Found file in tar: ${name} ({th.size} bytes, {pos} offset)"); if (FileIO._tar_filelist[(int)this.subdir].ContainsKey(name) == false) { FileIO._tar_filelist[(int)this.subdir].Add(name, entry); 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' */ link = th.linkname; if (name.Length == 0 || link.Length == 0) { break; } /* Convert to lowercase and our PATHSEPCHAR */ name = FileIO.SimplifyFileName(name); link = FileIO.SimplifyFileName(link); /* Only allow relative links */ if (link[0] == Path.PathSeparator) { Log.Debug($"Ignoring absolute link in tar: {name} . {link}"); break; } /* Process relative path. * Note: The destination of links must not contain any directory-links. */ dest = name; //var destIndex = dest.LastIndexOf(Path.PathSeparator); var destpos = dest; //if (destIndex >= 0) //{ // destpos = dest.Substring(destIndex + 1); //} //TODO THIS MAKES NO SENSE var linkParts = link.Split(Path.PathSeparator); foreach (var linkPart in linkParts) { if (linkPart == ".") { /* Skip '.' (current dir) */ } else if (linkPart == "..") { /* level up */ if (dest == "") { Log.Debug( $"Ignoring link pointing outside of data directory: {name} . {link}"); break; } /* Truncate 'dest' after last PATHSEPCHAR. * This assumes that the truncated part is a real directory and not a link. */ destpos = linkParts.Last(); break; } else { /* Append at end of 'dest' */ if (destpos.Any()) { destpos += Path.PathSeparator; } destpos = dest; } //if (destpos >= lastof(dest)) { // Log.Debug("The length of a link in tar-file '{filename}' is too large (malformed?)"); // f.Close(); // return false; //} } /* Store links in temporary list */ Log.Debug($"Found link in tar: {name} . {dest}"); links.Add(name, dest); break; } case '5': // directory /* Convert to lowercase and our PATHSEPCHAR */ name = FileIO.SimplifyFileName(name); /* Store the first directory name we detect */ Log.Debug($"Found dir in tar: {name}"); if (FileIO._tar_list[(int)this.subdir][filename].dirname == null) { FileIO._tar_list[(int)this.subdir][filename].dirname = name; } break; default: /* Ignore other types */ break; } /* Skip to the next block.. */ //var skip = Align(th.size, 512); if (f.Seek(th.size, SeekOrigin.Current) < 0) { Log.Debug($"The file '{filename}' can't be read as a valid tar-file"); f.Close(); return(false); } pos += th.size; } Log.Debug($"Found tar '{filename}' with {num} new files"); f.Close(); } } catch (IOException ex) { /* Although the file has been found there can be * a number of reasons we cannot open the file. * Most common case is when we simply have not * been given read access. */ Log.Error(ex); return(false); } /* 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. */ foreach (var link in links) { var src = link.Key; var dest = link.Value; FileIO.TarAddLink(src, dest, this.subdir); } return(true); }