1 // Copyright (C) 2010, 2011, 2014, 2015, 2020 Ben Asselstine
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 // 02110-1301, USA.
17
18 #include "tarhelper.h"
19 #include <algorithm>
20 #include <iostream>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <fstream>
26 #include "File.h"
27 #include <errno.h>
28 #include "ucompose.hpp"
29 #include <archive_entry.h>
30
31 /*
32 * libarchive doesn't rewind.
33 * once you go through the entries, that's it.
34 * to do it again you have to reopen.
35 *
36 * the Tar_Helper class works around this by delicately opening and closing
37 * the tar file for every operation on it.
38 * this has the unfortunate side effect when we add N files to a tar file
39 * using saveFile. the file gets saved out N seperate times.
40 *
41 * this class was originally implemented with libtar.
42 */
Tar_Helper(Glib::ustring file,std::ios::openmode mode,bool & broken)43 Tar_Helper::Tar_Helper(Glib::ustring file, std::ios::openmode mode, bool &broken)
44 {
45 t = NULL;
46 broken = Open(file, mode);
47 }
48
reopen(Tar_Helper * t)49 void Tar_Helper::reopen(Tar_Helper *t)
50 {
51 t->Close(false);
52 t->Open(t->pathname, t->openmode);
53 }
54
Open(Glib::ustring file,std::ios::openmode mode)55 bool Tar_Helper::Open(Glib::ustring file, std::ios::openmode mode)
56 {
57 t = NULL;
58 if (mode == std::ios::in && is_tarfile (file) == false)
59 return true;
60 //int m;
61 //int perms = 0;
62 openmode = mode;
63 if (mode & std::ios::in)
64 {
65 if (File::exists (file) == false)
66 return false;
67 t = archive_read_new ();
68 archive_read_support_format_tar(t);
69 int r = archive_read_open_filename(t, file.c_str(), 8192);
70 if (r != ARCHIVE_OK)
71 {
72 archive_read_free (t);
73 return false;
74 }
75 }
76 else if (mode & std::ios::out)
77 {
78 t = archive_write_new();
79 archive_write_add_filter_none(t);
80 archive_write_set_format_gnutar(t);
81 if (archive_write_open_filename(t, file.c_str()))
82 {
83 archive_write_free (t);
84 return false;
85 }
86 }
87 else
88 return false;
89
90 if (mode & std::ios::in)
91 {
92 tmpoutdir = File::getTarTempDir(File::get_basename(file,true));
93 File::create_dir(tmpoutdir);
94 }
95 else
96 tmpoutdir = "";
97 pathname = file;
98 return false;
99 }
100
dump_entry(struct archive * in,struct archive_entry * entry,struct archive * out)101 int Tar_Helper::dump_entry(struct archive *in, struct archive_entry *entry, struct archive *out)
102 {
103 archive_write_header(out, entry);
104
105 char buff[8192];
106 ssize_t len = archive_read_data(in, buff, sizeof (buff));
107 while (len > 0)
108 {
109 archive_write_data (out, buff, len);
110 len = archive_read_data(in, buff, sizeof (buff));
111 }
112 archive_write_finish_entry (out);
113 return ARCHIVE_OK;
114 }
115
saveFile(Tar_Helper * t,Glib::ustring filename,Glib::ustring destfile)116 bool Tar_Helper::saveFile(Tar_Helper *t, Glib::ustring filename, Glib::ustring destfile)
117 {
118 //save each already existing file entry out, and then add ours on the end.
119 //write the whole tar file to a temporary file and then copy it in place.
120 Glib::ustring tmp = File::get_tmp_file();
121 bool broken = false;
122 Tar_Helper out(tmp, std::ios::out, broken);
123 Tar_Helper in(t->pathname, std::ios::in, broken);
124 if (!broken)
125 {
126 struct archive_entry *in_entry = NULL;
127 while (1)
128 {
129 int r = archive_read_next_header(in.t, &in_entry);
130 if (r == ARCHIVE_EOF || r != ARCHIVE_OK)
131 break;
132 dump_entry(in.t, in_entry, out.t);
133 }
134 in.Close();
135 }
136
137 Glib::ustring b;
138 if (destfile == "")
139 b = File::get_basename(filename, true);
140 else
141 b = destfile;
142 struct archive_entry *entry = archive_entry_new();
143 dump_file_entry (filename, entry, b, out.t);
144
145 out.Close();
146 archive_entry_free (entry);
147 archive_write_free(t->t);
148 t->t = NULL;
149 File::copy(tmp, t->pathname);
150 File::erase(tmp);
151 return true;
152 }
153
saveFile(Glib::ustring filename,Glib::ustring destfile)154 bool Tar_Helper::saveFile(Glib::ustring filename, Glib::ustring destfile)
155 {
156 //archive_seek_data(t, 0, SEEK_SET);
157 return saveFile(this, filename, destfile);
158 }
159
Close(bool clean)160 void Tar_Helper::Close(bool clean)
161 {
162 if (t)
163 {
164 if (openmode & std::ios::out)
165 {
166 archive_write_close (t);
167 archive_write_free (t);
168 }
169 else if (openmode & std::ios::in)
170 {
171 archive_read_close (t);
172 archive_read_free (t);
173 }
174 t = NULL;
175 if (tmpoutdir != "" && clean)
176 File::clean_dir(tmpoutdir);
177 }
178 }
179
getFirstFile(std::list<Glib::ustring> exts,bool & broken)180 Glib::ustring Tar_Helper::getFirstFile(std::list<Glib::ustring> exts, bool &broken)
181 {
182 for (std::list<Glib::ustring>::iterator i = exts.begin(); i != exts.end(); i++)
183 {
184 Glib::ustring file = getFirstFile(*i, broken);
185 if (file != "")
186 return file;
187 }
188 return "";
189 }
190
getFirstFile(Glib::ustring extension,bool & broken)191 Glib::ustring Tar_Helper::getFirstFile(Glib::ustring extension, bool &broken)
192 {
193 std::list<Glib::ustring> files = getFilenames(extension);
194 if (files.size() == 0)
195 return "";
196 return getFile(files.front(), broken);
197 }
198
getFile(Tar_Helper * t,Glib::ustring filename,bool & broken,Glib::ustring tmpoutdir)199 Glib::ustring Tar_Helper::getFile(Tar_Helper *t, Glib::ustring filename, bool &broken, Glib::ustring tmpoutdir)
200 {
201 Glib::ustring f = File::getTempFile(tmpoutdir, filename);
202 if (File::exists(f) == true)
203 return f;
204 struct archive_entry *entry = NULL;
205 bool found = false;
206
207 reopen(t);
208 while (1)
209 {
210 int r = archive_read_next_header(t->t, &entry);
211 if (r == ARCHIVE_EOF)
212 break;
213 if (r != ARCHIVE_OK)
214 break;
215
216 if (filename == archive_entry_pathname(entry))
217 {
218 found = true;
219 break;
220 }
221 }
222 if (found)
223 {
224 const void *buff = NULL;
225 size_t size = 0;
226 int64_t offset = 0;
227 broken = false;
228 struct archive *ext = archive_write_disk_new();
229 archive_write_disk_set_options(ext,
230 ARCHIVE_EXTRACT_OWNER |
231 ARCHIVE_EXTRACT_PERM);
232 Glib::ustring outfile = File::getTempFile(tmpoutdir, filename);
233 archive_entry_copy_pathname(entry, outfile.c_str());
234
235 archive_write_header(ext, entry);
236
237 while (1)
238 {
239 int r = archive_read_data_block(t->t, &buff, &size, &offset);
240 if (r == ARCHIVE_EOF)
241 break;
242 if (r != ARCHIVE_OK)
243 break;
244 r = archive_write_data_block(ext, buff, size, offset);
245 if (r != ARCHIVE_OK)
246 break;
247 }
248 archive_write_finish_entry(ext);
249 archive_write_close(ext);
250 archive_write_free(ext);
251 return outfile;
252 }
253
254 return "";
255 }
256
getFile(Glib::ustring filename,bool & broken)257 Glib::ustring Tar_Helper::getFile(Glib::ustring filename, bool &broken)
258 {
259 return getFile(this, filename, broken, tmpoutdir);
260 }
261
getFilenames(Tar_Helper * t)262 std::list<Glib::ustring> Tar_Helper::getFilenames(Tar_Helper *t)
263 {
264 reopen(t);
265 std::list<Glib::ustring> result;
266 //archive_seek_data(t, 0, SEEK_SET);
267 struct archive_entry *entry = NULL;
268 while (1)
269 {
270 int r = archive_read_next_header(t->t, &entry);
271 if (r == ARCHIVE_EOF)
272 break;
273 if (r != ARCHIVE_OK)
274 break;
275
276 result.push_back(archive_entry_pathname(entry));
277 }
278 return result;
279 }
280
getFilenames()281 std::list<Glib::ustring> Tar_Helper::getFilenames()
282 {
283 return getFilenames(this);
284 }
285
getFirstFilename(Glib::ustring ext)286 Glib::ustring Tar_Helper::getFirstFilename(Glib::ustring ext)
287 {
288 std::list<Glib::ustring> result = getFilenames(ext);
289 if (result.empty())
290 return "";
291 return result.front();
292 }
293
getFilenames(Glib::ustring ext)294 std::list<Glib::ustring> Tar_Helper::getFilenames(Glib::ustring ext)
295 {
296 std::list<Glib::ustring> result;
297 std::list<Glib::ustring> f = getFilenames(this);
298 for (std::list<Glib::ustring>::iterator i = f.begin(); i != f.end(); i++)
299 {
300 if (ext == "")
301 result.push_back(*i);
302 else
303 if (File::nameEndsWith(*i, ext))
304 result.push_back(*i);
305 }
306 return result;
307 }
308
~Tar_Helper()309 Tar_Helper::~Tar_Helper()
310 {
311 if (t)
312 Close();
313 }
314
is_tarfile(Glib::ustring file)315 bool Tar_Helper::is_tarfile (Glib::ustring file)
316 {
317 bool retval = false;
318 struct archive *a = archive_read_new ();
319 archive_read_support_format_tar(a);
320 int r = archive_read_open_filename (a, file.c_str(), 10240);
321 struct archive_entry *entry = NULL;
322 archive_read_next_header(a, &entry);
323 if (r == ARCHIVE_OK)
324 {
325 retval = (archive_format (a) & ARCHIVE_FORMAT_TAR) > 0;
326 archive_read_close(a);
327 }
328 archive_read_free(a);
329 return retval;
330 }
331
dump_file_entry(Glib::ustring filename,struct archive_entry * entry,Glib::ustring nameinarchive,struct archive * out)332 int Tar_Helper::dump_file_entry (Glib::ustring filename, struct archive_entry *entry, Glib::ustring nameinarchive, struct archive *out)
333 {
334 struct stat st;
335 stat(filename.c_str(), &st);
336 archive_entry_copy_stat(entry, &st);
337 archive_entry_set_pathname(entry, nameinarchive.c_str());
338 archive_write_header(out, entry);
339 int fd = open (filename.c_str(), O_RDONLY);
340 if (fd < 0)
341 return ARCHIVE_FATAL;
342 char buff[8192];
343
344 ssize_t len = read (fd, buff, sizeof (buff));
345 while (len > 0)
346 {
347 archive_write_data (out, buff, len);
348 len = read (fd, buff, sizeof (buff));
349 }
350 archive_write_finish_entry(out);
351 close(fd);
352 return ARCHIVE_OK;
353 }
354
makeNameUnique(Glib::ustring name)355 Glib::ustring Tar_Helper::makeNameUnique(Glib::ustring name)
356 {
357 if (name.empty () == true)
358 return name;
359 std::list<Glib::ustring> files = getFilenames();
360 if (find(files.begin(), files.end(), name) == files.end())
361 return name;
362 Glib::ustring bname = File::get_basename (name, false);
363 Glib::ustring ext = File::get_extension (name);
364
365 //take any trailing numbers
366 int digits = 0;
367 for (Glib::ustring::reverse_iterator i = bname.rbegin ();
368 i != bname.rend(); i++)
369 {
370 if (g_unichar_isdigit (*i))
371 digits++;
372 else
373 break;
374 }
375 int count = 2;
376 if (digits > 0)
377 {
378 Glib::ustring numerals = bname.substr (bname.length () - digits);
379 bname = bname.substr (0, bname.length () - digits);
380 if (numerals.empty () == false)
381 count = atoi (numerals.c_str ()) + 1;
382 }
383 //okay we have to munge the name now.
384 while (1)
385 {
386 Glib::ustring newname = String::ucompose ("%1%2%3", bname, count, ext);
387 if (find(files.begin(), files.end(), newname) == files.end())
388 return newname;
389 count++;
390 if (count == 10000) //something has gone horribly wrong
391 break;
392 }
393 return name;
394 }
395
replaceFile(Glib::ustring filename,Glib::ustring newfilename,Glib::ustring archive_name)396 bool Tar_Helper::replaceFile(Glib::ustring filename, Glib::ustring newfilename, Glib::ustring archive_name)
397 {
398 if (newfilename != "" && File::exists(newfilename) == false)
399 return false;
400 //loop through existing file entries and copy them out.
401 //when we see the one we want to replace, we do so.
402 //unless newfilename is "", in which case we skip it (remove it).
403 //write the whole tar file to a temporary file and then copy it in place.
404 Glib::ustring tmp = File::get_tmp_file();
405 bool broken = false;
406 Tar_Helper out(tmp, std::ios::out, broken);
407 Tar_Helper in(pathname, std::ios::in, broken);
408 if (!broken)
409 {
410 struct archive_entry *in_entry = NULL;
411 while (1)
412 {
413 int r = archive_read_next_header(in.t, &in_entry);
414 if (r == ARCHIVE_EOF)
415 break;
416 if (r != ARCHIVE_OK)
417 break;
418 if (filename == Glib::ustring(archive_entry_pathname(in_entry)) &&
419 newfilename != "")
420 {
421 //hey it's the one we want to replace
422 dump_file_entry (newfilename, in_entry,
423 archive_name, out.t);
424 }
425 else if (filename == Glib::ustring(archive_entry_pathname(in_entry)) &&
426 newfilename == "")
427 ; //hey it's the one we're removing
428 else
429 dump_entry(in.t, in_entry, out.t);
430 }
431 in.Close();
432 }
433
434 out.Close();
435 if (filename == "")
436 {
437 Tar_Helper i(tmp, std::ios::in, broken);
438 saveFile (&i, newfilename, archive_name);
439 i.Close();
440 }
441 archive_write_free(t);
442 t = NULL;
443 bool ret = File::copy(tmp, pathname);
444 int save_errno = errno;
445 if (ret)
446 File::erase(tmp);
447 errno = save_errno;
448 return ret;
449 }
450
clean_tmp_dir(Glib::ustring filename)451 void Tar_Helper::clean_tmp_dir(Glib::ustring filename)
452 {
453 Glib::ustring tmpoutdir = File::getTarTempDir (File::get_basename(filename, true));
454 File::clean_dir(tmpoutdir);
455 }
456