ppp: minor cleanups to match other chatscripts order and documentation
[barry.git] / src / tarfile.cc
blob5aea5b13c54163fb08d0399bc0caa94edd38fa4e
1 ///
2 /// \file tarfile.cc
3 /// API for reading and writing sequentially from compressed
4 /// tar files.
6 /*
7 Copyright (C) 2007-2010, Chris Frey <cdfrey@foursquare.net>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "tarfile.h"
23 #include "data.h"
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <stdlib.h>
30 namespace reuse {
32 TarFile::TarFile(const char *filename,
33 bool create,
34 tartype_t *compress_ops,
35 bool always_throw)
36 : m_tar(0),
37 m_throw(always_throw),
38 m_writemode(create)
40 // figure out how to handle the file flags/modes
41 int flags = 0;
42 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
44 if( m_writemode ) {
45 flags = O_WRONLY | O_CREAT | O_EXCL;
47 else {
48 flags = O_RDONLY;
51 // open... throw on error, as we are in the constructor
52 if( tar_open(&m_tar, const_cast<char*>(filename),
53 compress_ops, flags, mode, TAR_VERBOSE | TAR_GNU) == -1 ) {
54 throw TarError(std::string("Unable to open tar file: ") + strerror(errno));
58 TarFile::~TarFile()
60 try {
61 Close();
62 } catch( TarError &te ) {}
65 bool TarFile::False(const char *msg)
67 m_last_error = msg;
68 if( m_throw )
69 throw TarError(msg);
70 else
71 return false;
74 bool TarFile::False(const std::string &msg, int err)
76 std::string str = msg;
77 str += ": ";
78 str += strerror(err);
79 return False(str);
82 bool TarFile::Close()
84 if( m_tar ) {
85 if( m_writemode ) {
86 if( tar_append_eof(m_tar) != 0 )
87 return False("Unable to write eof", errno);
90 if( tar_close(m_tar) != 0 ) {
91 return False("Unable to close file", errno);
93 m_tar = 0;
95 return true;
98 /// Appends a new file to the current tarfile, using tarpath as
99 /// its internal filename, and data as the complete file contents.
100 /// Uses current date and time as file mtime.
101 bool TarFile::AppendFile(const char *tarpath, const std::string &data)
103 // write standard file header
104 th_set_type(m_tar, REGTYPE);
105 th_set_mode(m_tar, 0644);
106 th_set_path(m_tar, const_cast<char*>(tarpath));
107 th_set_user(m_tar, 0);
108 th_set_group(m_tar, 0);
109 th_set_size(m_tar, data.size());
110 th_set_mtime(m_tar, time(NULL));
111 if( th_write(m_tar) != 0 ) {
112 return False("Unable to write tar header", errno);
115 // write the data in blocks until finished
116 char block[T_BLOCKSIZE];
117 for( size_t pos = 0; pos < data.size(); pos += T_BLOCKSIZE ) {
118 memset(block, 0, T_BLOCKSIZE);
120 size_t size = T_BLOCKSIZE;
121 if( data.size() - pos < T_BLOCKSIZE )
122 size = data.size() - pos;
124 memcpy(block, data.data() + pos, size);
126 if( tar_block_write(m_tar, block) != T_BLOCKSIZE ) {
127 return False("Unable to write block", errno);
131 return true;
134 /// Reads next available file into data, filling tarpath with
135 /// internal filename from tarball.
136 bool TarFile::ReadNextFile(std::string &tarpath, std::string &data)
138 // start fresh
139 tarpath.clear();
140 data.clear();
142 // read next tar file header... skip all directories
143 do {
144 if( th_read(m_tar) != 0 ) {
145 // this is not necessarily an error, as it could just
146 // be the end of file, so a simple false is good here,
147 // don't throw an exception
148 m_last_error = "";
149 return false;
151 } while( TH_ISDIR(m_tar) );
153 // write standard file header
154 if( !TH_ISREG(m_tar) ) {
155 return False("Only regular files are supported inside a tarball.");
158 char *pathname = th_get_pathname(m_tar);
159 tarpath = pathname;
161 // FIXME (leak) - someday, when all distros use a patched version of
162 // libtar, we may be able to avoid this memory leak, but
163 // th_get_pathname() does not consistently return a user-freeable
164 // string on all distros.
166 // See the following links for more information:
167 // https://bugs.launchpad.net/ubuntu/+source/libtar/+bug/41804
168 // https://lists.feep.net:8080/pipermail/libtar/2006-April/000222.html
170 // free(pathname);
171 size_t size = th_get_size(m_tar);
173 // read the data in blocks until finished
174 char block[T_BLOCKSIZE];
175 for( size_t pos = 0; pos < size; pos += T_BLOCKSIZE ) {
176 memset(block, 0, T_BLOCKSIZE);
178 size_t readsize = T_BLOCKSIZE;
179 if( size - pos < T_BLOCKSIZE )
180 readsize = size - pos;
182 if( tar_block_read(m_tar, block) != T_BLOCKSIZE ) {
183 return False("Unable to read block", errno);
186 data.append(block, readsize);
189 return true;
192 // FIXME - yes, this is blatant copying of code, but this is
193 // specific to Barry, to use a Barry::Data object instead of std::string
194 // in order to reduce copies.
195 bool TarFile::ReadNextFile(std::string &tarpath, Barry::Data &data)
197 // start fresh
198 tarpath.clear();
199 data.QuickZap();
201 // read next tar file header... skip all directories
202 do {
203 if( th_read(m_tar) != 0 ) {
204 // this is not necessarily an error, as it could just
205 // be the end of file, so a simple false is good here,
206 // don't throw an exception
207 m_last_error = "";
208 return false;
210 } while( TH_ISDIR(m_tar) );
212 // write standard file header
213 if( !TH_ISREG(m_tar) ) {
214 return False("Only regular files are supported inside a tarball.");
217 char *pathname = th_get_pathname(m_tar);
218 tarpath = pathname;
220 // FIXME (leak) - someday, when all distros use a patched version of
221 // libtar, we may be able to avoid this memory leak, but
222 // th_get_pathname() does not consistently return a user-freeable
223 // string on all distros.
225 // See the following links for more information:
226 // https://bugs.launchpad.net/ubuntu/+source/libtar/+bug/41804
227 // https://lists.feep.net:8080/pipermail/libtar/2006-April/000222.html
229 // free(pathname);
230 size_t size = th_get_size(m_tar);
232 // read the data in blocks until finished
233 char block[T_BLOCKSIZE];
234 for( size_t pos = 0; pos < size; pos += T_BLOCKSIZE ) {
235 memset(block, 0, T_BLOCKSIZE);
237 size_t readsize = T_BLOCKSIZE;
238 if( size - pos < T_BLOCKSIZE )
239 readsize = size - pos;
241 if( tar_block_read(m_tar, block) != T_BLOCKSIZE ) {
242 return False("Unable to read block", errno);
245 data.Append(block, readsize);
248 return true;
251 /// Read next available filename, skipping the data if it is
252 /// a regular file
253 bool TarFile::ReadNextFilenameOnly(std::string &tarpath)
255 // start fresh
256 tarpath.clear();
258 // read next tar file header... skip all directories
259 do {
260 if( th_read(m_tar) != 0 ) {
261 // this is not necessarily an error, as it could just
262 // be the end of file, so a simple false is good here,
263 // don't throw an exception
264 m_last_error = "";
265 return false;
267 } while( TH_ISDIR(m_tar) );
269 // write standard file header
270 if( !TH_ISREG(m_tar) ) {
271 return False("Only regular files are supported inside a tarball.");
274 char *pathname = th_get_pathname(m_tar);
275 tarpath = pathname;
276 // See above FIXME (leak) comment
277 // free(pathname);
279 if( tar_skip_regfile(m_tar) != 0 ) {
280 return False("Unable to skip tar file", errno);
283 return true;
287 } // namespace reuse
290 #ifdef __TEST_MODE__
292 #include <iostream>
293 #include <unistd.h>
295 using namespace std;
297 int main()
299 try {
300 cout << "Writing test file..." << endl;
301 reuse::TarFile output("tartest.tar.gz", true, true, true);
302 std::string data;
303 for( int i = 0; i < 60; i++ ) {
304 data.append("0123456789", 10);
307 output.AppendFile("path1/test1.txt", data);
308 output.AppendFile("path2/test2.txt", data);
309 output.Close();
312 cout << "Reading test file..." << endl;
313 reuse::TarFile input("tartest.tar.gz", false, true, true);
314 std::string path, incoming;
316 while( input.ReadNextFile(path, incoming) ) {
317 cout << "Read: " << path
318 << " Data: "
319 << (( data == incoming ) ? "equal" : "different")
320 << endl;
323 input.Close();
325 unlink("tartest.tar.gz");
327 } catch( reuse::TarFile::TarError &te ) {
328 cerr << te.what() << endl;
329 return 1;
333 #endif