lib: add prefixes to enums to avoid name clashes on Windows (like ERROR)
[barry.git] / src / tarfile.cc
blobda0d07d8b0eac952b7430ecc9616f97dcd7f19d1
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"
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <stdlib.h>
29 namespace reuse {
31 TarFile::TarFile(const char *filename,
32 bool create,
33 tartype_t *compress_ops,
34 bool always_throw)
35 : m_tar(0),
36 m_throw(always_throw),
37 m_writemode(create)
39 // figure out how to handle the file flags/modes
40 int flags = 0;
41 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
43 if( m_writemode ) {
44 flags = O_WRONLY | O_CREAT | O_EXCL;
46 else {
47 flags = O_RDONLY;
50 // open... throw on error, as we are in the constructor
51 if( tar_open(&m_tar, const_cast<char*>(filename),
52 compress_ops, flags, mode, TAR_VERBOSE | TAR_GNU) == -1 ) {
53 throw TarError(std::string("Unable to open tar file: ") + strerror(errno));
57 TarFile::~TarFile()
59 try {
60 Close();
61 } catch( TarError &te ) {}
64 bool TarFile::False(const char *msg)
66 m_last_error = msg;
67 if( m_throw )
68 throw TarError(msg);
69 else
70 return false;
73 bool TarFile::False(const std::string &msg, int err)
75 std::string str = msg;
76 str += ": ";
77 str += strerror(err);
78 return False(str);
81 bool TarFile::Close()
83 if( m_tar ) {
84 if( m_writemode ) {
85 if( tar_append_eof(m_tar) != 0 )
86 return False("Unable to write eof", errno);
89 if( tar_close(m_tar) != 0 ) {
90 return False("Unable to close file", errno);
92 m_tar = 0;
94 return true;
97 /// Appends a new file to the current tarfile, using tarpath as
98 /// its internal filename, and data as the complete file contents.
99 /// Uses current date and time as file mtime.
100 bool TarFile::AppendFile(const char *tarpath, const std::string &data)
102 // write standard file header
103 th_set_type(m_tar, REGTYPE);
104 th_set_mode(m_tar, 0644);
105 th_set_path(m_tar, const_cast<char*>(tarpath));
106 th_set_user(m_tar, 0);
107 th_set_group(m_tar, 0);
108 th_set_size(m_tar, data.size());
109 th_set_mtime(m_tar, time(NULL));
110 if( th_write(m_tar) != 0 ) {
111 return False("Unable to write tar header", errno);
114 // write the data in blocks until finished
115 char block[T_BLOCKSIZE];
116 for( size_t pos = 0; pos < data.size(); pos += T_BLOCKSIZE ) {
117 memset(block, 0, T_BLOCKSIZE);
119 size_t size = T_BLOCKSIZE;
120 if( data.size() - pos < T_BLOCKSIZE )
121 size = data.size() - pos;
123 memcpy(block, data.data() + pos, size);
125 if( tar_block_write(m_tar, block) != T_BLOCKSIZE ) {
126 return False("Unable to write block", errno);
130 return true;
133 /// Reads next available file into data, filling tarpath with
134 /// internal filename from tarball.
135 bool TarFile::ReadNextFile(std::string &tarpath, std::string &data)
137 // start fresh
138 tarpath.clear();
139 data.clear();
141 // read next tar file header
142 if( th_read(m_tar) != 0 ) {
143 // this is not necessarily an error, as it could just
144 // be the end of file, so a simple false is good here,
145 // don't throw an exception
146 m_last_error = "";
147 return false;
150 // write standard file header
151 if( !TH_ISREG(m_tar) ) {
152 return False("Only regular files are supported inside a tarball.");
155 char *pathname = th_get_pathname(m_tar);
156 tarpath = pathname;
158 // FIXME (leak) - someday, when all distros use a patched version of
159 // libtar, we may be able to avoid this memory leak, but
160 // th_get_pathname() does not consistently return a user-freeable
161 // string on all distros.
163 // See the following links for more information:
164 // https://bugs.launchpad.net/ubuntu/+source/libtar/+bug/41804
165 // https://lists.feep.net:8080/pipermail/libtar/2006-April/000222.html
167 // free(pathname);
168 size_t size = th_get_size(m_tar);
170 // read the data in blocks until finished
171 char block[T_BLOCKSIZE];
172 for( size_t pos = 0; pos < size; pos += T_BLOCKSIZE ) {
173 memset(block, 0, T_BLOCKSIZE);
175 size_t readsize = T_BLOCKSIZE;
176 if( size - pos < T_BLOCKSIZE )
177 readsize = size - pos;
179 if( tar_block_read(m_tar, block) != T_BLOCKSIZE ) {
180 return False("Unable to read block", errno);
183 data.append(block, readsize);
186 return true;
189 /// Read next available filename, skipping the data if it is
190 /// a regular file
191 bool TarFile::ReadNextFilenameOnly(std::string &tarpath)
193 // start fresh
194 tarpath.clear();
196 // read next tar file header
197 if( th_read(m_tar) != 0 ) {
198 // this is not necessarily an error, as it could just
199 // be the end of file, so a simple false is good here,
200 // don't throw an exception
201 m_last_error = "";
202 return false;
205 // write standard file header
206 if( !TH_ISREG(m_tar) ) {
207 return False("Only regular files are supported inside a tarball.");
210 char *pathname = th_get_pathname(m_tar);
211 tarpath = pathname;
212 // See above FIXME (leak) comment
213 // free(pathname);
215 if( tar_skip_regfile(m_tar) != 0 ) {
216 return False("Unable to skip tar file", errno);
219 return true;
223 } // namespace reuse
226 #ifdef __TEST_MODE__
228 #include <iostream>
229 #include <unistd.h>
231 using namespace std;
233 int main()
235 try {
236 cout << "Writing test file..." << endl;
237 reuse::TarFile output("tartest.tar.gz", true, true, true);
238 std::string data;
239 for( int i = 0; i < 60; i++ ) {
240 data.append("0123456789", 10);
243 output.AppendFile("path1/test1.txt", data);
244 output.AppendFile("path2/test2.txt", data);
245 output.Close();
248 cout << "Reading test file..." << endl;
249 reuse::TarFile input("tartest.tar.gz", false, true, true);
250 std::string path, incoming;
252 while( input.ReadNextFile(path, incoming) ) {
253 cout << "Read: " << path
254 << " Data: "
255 << (( data == incoming ) ? "equal" : "different")
256 << endl;
259 input.Close();
261 unlink("tartest.tar.gz");
263 } catch( reuse::TarFile::TarError &te ) {
264 cerr << te.what() << endl;
265 return 1;
269 #endif