1 /*********************************************************************/
2 // dar - disk archive - a backup/restoration program
3 // Copyright (C) 2002-2052 Denis Corbin
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 //
19 // to contact the author : http://dar.linux.free.fr/email.html
20 /*********************************************************************/
21 
22 #include "../my_config.h"
23 
24 extern "C"
25 {
26 #if HAVE_ERRNO_H
27 #include <errno.h>
28 #endif
29 
30 #if HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 
34 #if HAVE_FCNTL_H
35 #include <fcntl.h>
36 #endif
37 
38 #if HAVE_STRING_H
39 #include <string.h>
40 #endif
41 
42 } // end extern "C"
43 
44 #include "mask_list.hpp"
45 #include "erreurs.hpp"
46 #include "tools.hpp"
47 #include "cygwin_adapt.hpp"
48 #include "fichier_local.hpp"
49 #include "nls_swap.hpp"
50 
51 using namespace std;
52 
53 namespace libdar
54 {
55 
56     static bool modified_lexicalorder_a_lessthan_b(const std::string & a, const std::string & b);
57 
mask_list(const string & filename_list_st,bool case_sensit,const path & prefix_t,bool include)58     mask_list::mask_list(const string & filename_list_st, bool case_sensit, const path & prefix_t, bool include)
59     {
60 	NLS_SWAP_IN;
61 	try
62 	{
63 	    try
64 	    {
65 		case_s = case_sensit;              //< object's field
66 		including = include;               //< object's field
67 		fichier_local source = filename_list_st; //< where we read data from
68 		char *buffer = nullptr;               //< hold the just read data
69 		static const U_I buf_size = 20480; //< size of buffer: we read at most this number of bytes at a time
70 		list <string> tmp;                 //< list of all raw lines read, without any prefix
71 		U_I lu = 0, curs;                  //< cursor used as cisors to split data in line
72 		char *beg = nullptr;                  //< points to the beginning of the next line inside buffer, when more than one line can be found in buffer
73 		string str_beg;                    //< holds the std::string copy of beg, eventually uppercased
74 		string current_entry = "";         //< holds the current line converted to string between each read()
75 		path prefix = prefix_t;            //< the prefix to add to relative paths
76 
77 
78 
79 		    /////////////
80 		    // changing the prefix to uppercase if case sensitivity is disabled
81 
82 		if(!case_sensit)
83 		{
84 		    string ptp = prefix_t.display();
85 		    string upper;
86 		    tools_to_upper(ptp, upper);
87 		    prefix = path(upper);
88 		}
89 
90 
91 
92 		    /////////////
93 		    // building buffer that will be used to split read data line by line
94 
95 		meta_new(buffer, buf_size+1); // one char more to be able to add a '\0' if necessary
96 		if(buffer == nullptr)
97 		    throw Erange("mask_list::mask_list", tools_printf(gettext("Cannot allocate memory for buffer while reading %S"), &filename_list_st));
98 
99 
100 
101 		    /////////////
102 		    // filling 'tmp' with with each line read
103 
104 		try
105 		{
106 		    do
107 		    {
108 			lu = source.read(buffer, buf_size);
109 
110 			if(lu > 0)
111 			{
112 			    curs = 0;
113 			    beg = buffer;
114 
115 			    do
116 			    {
117 				while(curs < lu && buffer[curs] != '\n' && buffer[curs] != '\0')
118 				    curs++;
119 
120 				if(curs < lu)
121 				{
122 				    if(buffer[curs] == '\0')
123 					throw Erange("mask_list::mask_list", tools_printf(gettext("Found '\0' character in %S, not a plain file, aborting"), &filename_list_st));
124 				    if(buffer[curs] == '\n')
125 				    {
126 					buffer[curs] = '\0';
127 					if(!case_s)
128 					    tools_to_upper(beg, str_beg);
129 					else
130 					    str_beg = string(beg);
131 					current_entry += str_beg;
132 					if(current_entry != "")
133 					    tmp.push_back(current_entry);
134 					current_entry = "";
135 					curs++;
136 					beg = buffer + curs;
137 				    }
138 				    else
139 					throw SRC_BUG;
140 				}
141 				else // reached end of buffer
142 				{
143 				    if(lu == buf_size && beg == buffer)
144 				    {
145 					buffer[buf_size - 1] = '\0';
146 					throw Erange("mask_list::mask_list",
147 						     tools_printf(gettext("line exceeding the maximum of %d characters in listing file %S, aborting. Concerned line starts with: %s"),
148 								  buf_size - 1,
149 								  &filename_list_st,
150 								  buffer));
151 				    }
152 				    buffer[lu] = '\0';
153 				    if(!case_s)
154 					tools_to_upper(beg, str_beg);
155 				    else
156 					str_beg = string(beg);
157 				    current_entry += str_beg;
158 				}
159 			    }
160 			    while(curs < lu);
161 			}
162 		    }
163 		    while(lu > 0);
164 
165 		    if(current_entry != "")
166 			tmp.push_back(current_entry);
167 		}
168 		catch(...)
169 		{
170 		    meta_delete(buffer);
171 		    throw;
172 		}
173 		meta_delete(buffer);
174 		buffer = nullptr;
175 
176 
177 		    /////////////
178 		    // completing relative paths of the list
179 
180 		if(prefix.is_relative() && !prefix.is_subdir_of("<ROOT>", true))
181 		    throw Erange("mask_list::mask_list", gettext("Mask_list's prefix must be an absolute path or start with \"<ROOT>\" string for archive merging"));
182 		else
183 		{
184 		    path current = "/";
185 		    list <string>::iterator it = tmp.begin();
186 
187 		    while(it != tmp.end())
188 		    {
189 			try
190 			{
191 			    current = *it;
192 			    if(current.is_relative())
193 			    {
194 				current = prefix + current;
195 				*it = current.display();
196 			    }
197 			}
198 			catch(Egeneric & e)
199 			{
200 			    string err = e.get_message();
201 			    string line = *it;
202 
203 			    throw Erange("mask_list::mask_list", tools_printf(gettext("Error met while reading line\n\t%S\n from file %S: %S"), &line, &filename_list_st, &err));
204 			}
205 			it++;
206 		    }
207 		}
208 
209 		    /////////////
210 		    // sorting the list of entry
211 
212 		    // sorting the list with a modified lexicographical order where the / as is lowest character, other letter order unchanged
213 		tmp.sort(&modified_lexicalorder_a_lessthan_b);
214 		tmp.unique(); // remove duplicates
215 
216 		    // converting the sorted list to vector, to get the indexing feature of this type
217 		contenu.assign(tmp.begin(), tmp.end());
218 		taille = contenu.size();
219 		if(taille < contenu.size())
220 		    throw Erange("mask_list::mask_list", tools_printf(gettext("Too much line in file %S (integer overflow)"), &filename_list_st));
221 	    }
222 	    catch(Egeneric & e)
223 	    {
224 		e.prepend_message(tools_printf(gettext("Error met while opening %S: "), &filename_list_st));
225 		throw;
226 	    }
227 	}
228 	catch(...)
229 	{
230 	    NLS_SWAP_OUT;
231 	    throw;
232 	}
233 	NLS_SWAP_OUT;
234     }
235 
is_covered(const string & expression) const236     bool mask_list::is_covered(const string & expression) const
237     {
238 	if(taille == 0)
239 	    return false;
240 
241 	U_I min = 0, max = taille-1, tmp;
242         string target;
243         bool ret;
244 
245         if(case_s)
246             target = expression;
247         else
248             tools_to_upper(expression, target);
249 
250             // divide & conquer algorithm on a sorted list (aka binary search)
251         while(max - min > 1)
252         {
253             tmp = (min + max)/2;
254             if(modified_lexicalorder_a_lessthan_b(contenu[tmp], target))
255                 min = tmp;
256             else
257                 if(contenu[tmp] == target)
258                     max = min = tmp;
259                 else
260                     max = tmp;
261         }
262 	if(min == 0 && modified_lexicalorder_a_lessthan_b(target, contenu[min]))
263 	    max = min;
264 
265         ret = contenu[max] == target || contenu[min] == target;
266         if(including && !ret) // if including files, we must also include directories leading to a listed file
267 	{
268 	    string c_max = contenu[max];
269             ret = path(c_max).is_subdir_of(expression, case_s);
270 	}
271 
272         return ret;
273     }
274 
dump(const string & prefix) const275     string mask_list::dump(const string & prefix) const
276     {
277 	vector<string>::const_iterator it = contenu.begin();
278 	string rec_pref = prefix + "  | ";
279 
280 	string ret = prefix + "If matches one of the following line(s):\n";
281 	while(it != contenu.end())
282 	{
283 	    ret += rec_pref + *it + "\n";
284 	    ++it;
285 	}
286 	ret += prefix + "  +--";
287 
288 	return ret;
289     }
290 
291 
modified_lexicalorder_a_lessthan_b(const string & a,const string & b)292     static bool modified_lexicalorder_a_lessthan_b(const string & a, const string & b)
293     {
294 	string::const_iterator at = a.begin();
295 	string::const_iterator bt = b.begin();
296 
297 	while(at != a.end() && bt != b.end())
298 	{
299 	    if(*at == '/')
300 	    {
301 		if(*bt != '/')
302 		    return true;
303 
304 		    // else both a and b current letter are equal to '/'
305 		    // reading further
306 	    }
307 	    else
308 	    {
309 		if(*bt == '/')
310 		    return false;
311 		else
312 		{
313 		    if(*at != *bt)
314 			return *at < *bt;
315 
316 			// else a and b letter are equal
317 			// reading further to find a difference
318 		}
319 	    }
320 
321 	    ++at;
322 	    ++bt;
323 	}
324 
325 	if(at == a.end())
326 	    return true; // even if bt == b.end(), we have a <= b
327 
328 	if(bt == b.end())
329 	    return false; // because at != a.end(), we thus have neither a == b nor a < b
330 	else
331 	    throw SRC_BUG; // at != a.end() and bt != b.end() how did we escaped the while loop?
332     }
333 
334 } // end of namespace
335