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