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 // to allow compilation under Cygwin we need <sys/types.h>
27 // else Cygwin's <netinet/in.h> lack __int16_t symbol !?!
28 #if HAVE_SYS_TYPES_H
29 #include <sys/types.h>
30 #endif
31 
32 #if HAVE_NETINET_IN_H
33 #include <netinet/in.h>
34 #endif
35 
36 #if HAVE_ARPA_INET_H
37 #include <arpa/inet.h>
38 #endif
39 } // end extern "C"
40 
41 #include <iomanip>
42 
43 #include "data_tree.hpp"
44 #include "tools.hpp"
45 #include "user_interaction.hpp"
46 #include "deci.hpp"
47 #include "path.hpp"
48 #include "datetime.hpp"
49 #include "cat_all_entrees.hpp"
50 
51 using namespace std;
52 using namespace libdar;
53 
54 static data_tree *read_from_file(generic_file & f, unsigned char db_version, memory_pool *pool);
55 static void read_from_file(generic_file &f, archive_num &a);
56 static void write_to_file(generic_file &f, archive_num a);
57 static void display_line(user_interaction & dialog,
58 			 archive_num num,
59 			 const datetime *data,
60 			 data_tree::etat data_presence,
61 			 const datetime *ea,
62 			 data_tree::etat ea_presence);
63 
64 #define ETAT_SAVED 'S'
65 #define ETAT_PRESENT 'P'
66 #define ETAT_REMOVED 'R'
67 
68 namespace libdar
69 {
70 
dump(generic_file & f) const71     void data_tree::status::dump(generic_file & f) const
72     {
73 	date.dump(f);
74 	switch(present)
75 	{
76 	case et_saved:
77 	    f.write("S", 1);
78 	    break;
79 	case et_present:
80 	    f.write("P", 1);
81 	    break;
82 	case et_removed:
83 	    f.write("R", 1);
84 	    break;
85 	case et_absent:
86 	    f.write("A", 1);
87 	    break;
88 	default:
89 	    throw SRC_BUG;
90 	}
91     }
92 
read(generic_file & f,unsigned char db_version)93     void data_tree::status::read(generic_file & f, unsigned char db_version)
94     {
95 	char tmp;
96 
97 	date.read(f, db2archive_version(db_version));
98 	if(f.read(&tmp, 1) != 1)
99 	    throw Erange("data_tree::status::read", gettext("reached End of File before all expected data could be read"));
100 	switch(tmp)
101 	{
102 	case 'S':
103 	    present = et_saved;
104 	    break;
105 	case 'P':
106 	    present = et_present;
107 	    break;
108 	case 'R':
109 	    present = et_removed;
110 	    break;
111 	case 'A':
112 	    present = et_absent;
113 	    break;
114 	default:
115 	    throw Erange("data_tree::status::read", gettext("Unexpected value found in database"));
116 	}
117     }
118 
data_tree(const string & name)119     data_tree::data_tree(const string & name)
120     {
121 	filename = name;
122     }
123 
data_tree(generic_file & f,unsigned char db_version)124     data_tree::data_tree(generic_file & f, unsigned char db_version)
125     {
126 	archive_num k;
127 	status sta;
128 
129 	    // signature has already been read
130 	tools_read_string(f, filename);
131 	infinint tmp = infinint(f); // number of entry in last_mod map
132 	while(!tmp.is_zero())
133 	{
134 	    read_from_file(f, k);
135 	    switch(db_version)
136 	    {
137 	    case 1:
138 		sta.date = infinint(f);
139 		sta.present = et_saved;
140 		last_mod[k] = sta;
141 		break;
142 	    case 2:
143 	    case 3:
144 	    case 4:
145 		sta.read(f, db_version);
146 		last_mod[k] = sta;
147 		break;
148 	    default: // unsupported database version
149 		throw SRC_BUG; // this statement should have been prevented earlier in the code to avoid destroying or loosing data from the database
150 	    }
151 	    --tmp;
152 	}
153 
154 	tmp = infinint(f); // number of entry in last_change map
155 	while(!tmp.is_zero())
156 	{
157 	    read_from_file(f, k);
158 	    switch(db_version)
159 	    {
160 	    case 1:
161 		sta.date = infinint(f);
162 		sta.present = et_saved;
163 		last_change[k] = sta;
164 		break;
165 	    case 2:
166 	    case 3:
167 	    case 4:
168 		sta.read(f, db_version);
169 		last_change[k] = sta;
170 		break;
171 	    default:
172 		throw SRC_BUG;
173 	    }
174 	    --tmp;
175 	}
176     }
177 
dump(generic_file & f) const178     void data_tree::dump(generic_file & f) const
179     {
180 	char tmp = obj_signature();
181 	infinint sz;
182 	map<archive_num, status>::const_iterator it = last_mod.begin();
183 
184 	f.write(&tmp, 1);
185 	tools_write_string(f, filename);
186 
187 	sz = last_mod.size();
188 	sz.dump(f);
189 	while(it != last_mod.end())
190 	{
191 	    write_to_file(f, it->first); // key
192 	    it->second.dump(f); // value
193 	    ++it;
194 	}
195 
196 	sz = last_change.size();
197 	sz.dump(f);
198 	it = last_change.begin();
199 	while(it != last_change.end())
200 	{
201 	    write_to_file(f, it->first); // key
202 	    it->second.dump(f); // value
203 	    ++it;
204 	}
205     }
206 
207 
get_data(archive_num & archive,const datetime & date,bool even_when_removed) const208     data_tree::lookup data_tree::get_data(archive_num & archive, const datetime & date, bool even_when_removed) const
209     {
210 	map<archive_num, status>::const_iterator it = last_mod.begin();
211 	datetime max_seen = datetime(0), max_real = datetime(0);
212 	    //archive (first argument) carries the last archive number (in the order of the database) in which a valid entry has been
213 	    //found, but which is not just a record "et_present" meaning that the file existed but has not been saved (differential backup context) or "et_absent" (no entry in the corresponding archive).
214 	archive_num archive_seen = 0; //< last archive number (in the order of the database) in which a valid entry has been found (any state)
215 	archive_num archive_even_when_removed = 0; //< last archive number in which a valid entry with data available has been found
216 	bool presence_seen = false; //< whether the last found valid entry indicates file was present or removed
217 	bool presence_real = false; //< whether the last found valid entry not being in an "et_present" state was present or removed
218 	lookup ret;
219 
220 	archive = 0; // 0 is never assigned to an archive number
221 	while(it != last_mod.end())
222 	{
223 	    if(it->second.date >= max_seen  // > and = because there should never be twice the same value
224 		   // and we must be able to see a value of 0 (initially max = 0) which is valid.
225 	       && (date.is_null() || it->second.date <= date))
226 	    {
227 		max_seen = it->second.date;
228 		archive_seen = it->first;
229 		switch(it->second.present)
230 		{
231 		case et_saved:
232 		case et_present:
233 		    presence_seen = true;
234 		    break;
235 		case et_removed:
236 		case et_absent:
237 		    presence_seen = false;
238 		    break;
239 		default:
240 		    throw SRC_BUG;
241 		}
242 	    }
243 
244 	    if(it->second.date >= max_real  // > and = because there should never be twice the same value
245 		   // and we must be able to see a value of 0 (initially max = 0) which is valid.
246 	       && (date.is_null() || it->second.date <= date))
247 	    {
248 		if(it->second.present != et_present)
249 		{
250 		    max_real = it->second.date;
251 		    archive = it->first;
252 		    switch(it->second.present)
253 		    {
254 		    case et_saved:
255 			presence_real = true;
256 			archive_even_when_removed = archive;
257 			break;
258 		    case et_removed:
259 		    case et_absent:
260 			presence_real = false;
261 			break;
262 		    case et_present:
263 			throw SRC_BUG;
264 		    default:
265 			throw SRC_BUG;
266 		    }
267 		}
268 	    }
269 
270 	    ++it;
271 	}
272 
273 	if(even_when_removed && archive_even_when_removed > 0)
274 	{
275 	    archive = archive_even_when_removed;
276 	    presence_seen = presence_real = true;
277 	}
278 
279 	if(archive == 0)
280 	    if(archive_seen != 0) // last acceptable entry is a file present but not saved, but no full backup is present in a previous archive
281 	    {
282 		archive = archive_seen;
283 		ret = not_restorable;
284 	    }
285 	    else
286 		ret = not_found;
287 	else
288 	    if(archive_seen != 0)
289 		if(presence_seen && !presence_real) // last acceptable entry is a file present but not saved,
290 			// but no full backup is present in a previous archive
291 		{
292 		    archive = archive_seen;
293 		    ret = not_restorable;
294 		}
295 		else
296 		    if(presence_seen != presence_real)
297 			throw SRC_BUG;
298 		    else  // archive > 0 && presence_seen == present_real
299 			if(presence_real)
300 			    ret = found_present;
301 			else
302 			    ret = found_removed;
303 	    else // archive != 0 but archive_seen == 0
304 		throw SRC_BUG;
305 
306 
307 	return ret;
308     }
309 
get_EA(archive_num & archive,const datetime & date,bool even_when_removed) const310     data_tree::lookup data_tree::get_EA(archive_num & archive, const datetime & date, bool even_when_removed) const
311     {
312 	map<archive_num, status>::const_iterator it = last_change.begin();
313 	datetime max_seen = datetime(0), max_real = datetime(0);
314 	bool presence_seen = false, presence_real = false;
315 	archive_num archive_seen = 0;
316 	archive_num archive_even_when_removed = 0;
317 	lookup ret;
318 
319 	archive = 0; // 0 is never assigned to an archive number
320 	while(it != last_change.end())
321 	{
322 	    if(it->second.date >= max_seen  // > and = because there should never be twice the same value
323 		   // and we must be able to see a value of 0 (initially max = 0) which is valid.
324 	       && (date.is_null() || it->second.date <= date))
325 	    {
326 		max_seen = it->second.date;
327 		archive_seen = it->first;
328 		switch(it->second.present)
329 		{
330 		case et_saved:
331 		case et_present:
332 		    presence_seen = true;
333 		    break;
334 		case et_removed:
335 		case et_absent:
336 		    presence_seen = false;
337 		    break;
338 		default:
339 		    throw SRC_BUG;
340 		}
341 	    }
342 
343 	    if(it->second.date >= max_real  // > and = because there should never be twice the same value
344 		   // and we must be able to see a value of 0 (initially max = 0) which is valid.
345 	       && (date.is_null() || it->second.date <= date))
346 	    {
347 		if(it->second.present != et_present)
348 		{
349 		    max_real = it->second.date;
350 		    archive = it->first;
351 		    switch(it->second.present)
352 		    {
353 		    case et_saved:
354 			presence_real = true;
355 			archive_even_when_removed = archive;
356 			break;
357 		    case et_removed:
358 		    case et_absent:
359 			presence_real = false;
360 			break;
361 		    case et_present:
362 			throw SRC_BUG;
363 		    default:
364 			throw SRC_BUG;
365 		    }
366 		}
367 	    }
368 
369 	    ++it;
370 	}
371 
372 	if(even_when_removed && archive_even_when_removed > 0)
373 	{
374 	    archive = archive_even_when_removed;
375 	    presence_seen = presence_real = true;
376 	}
377 
378 
379 	if(archive == 0)
380 	    if(archive_seen != 0) // last acceptable entry is a file present but not saved, but no full backup is present in a previous archive
381 		ret = not_restorable;
382 	    else
383 		ret = not_found;
384 	else
385 	    if(archive_seen != 0)
386 		if(presence_seen && !presence_real) // last acceptable entry is a file present but not saved, but no full backup is present in a previous archive
387 		    ret = not_restorable;
388 		else
389 		    if(presence_seen != presence_real)
390 			throw SRC_BUG;
391 		    else  // archive > 0 && presence_seen == present_real
392 			if(presence_real)
393 			    ret = found_present;
394 			else
395 			    ret = found_removed;
396 	    else
397 		throw SRC_BUG;
398 
399 	return ret;
400     }
401 
read_data(archive_num num,datetime & val,etat & present) const402     bool data_tree::read_data(archive_num num, datetime & val, etat & present) const
403     {
404 	map<archive_num, status>::const_iterator it = last_mod.find(num);
405 
406 	if(it != last_mod.end())
407 	{
408 	    val = it->second.date;
409 	    present = it->second.present;
410 	    return true;
411 	}
412 	else
413 	    return false;
414     }
415 
read_EA(archive_num num,datetime & val,etat & present) const416     bool data_tree::read_EA(archive_num num, datetime & val, etat & present) const
417     {
418 	map<archive_num, status>::const_iterator it = last_change.find(num);
419 
420 	if(it != last_change.end())
421 	{
422 	    val = it->second.date;
423 	    present = it->second.present;
424 	    return true;
425 	}
426 	else
427 	    return false;
428     }
429 
finalize(const archive_num & archive,const datetime & deleted_date,const archive_num & ignore_archives_greater_or_equal)430     void data_tree::finalize(const archive_num & archive,
431 			     const datetime & deleted_date,
432 			     const archive_num & ignore_archives_greater_or_equal)
433     {
434 	map<archive_num, status>::iterator it = last_mod.begin();
435 	bool presence_max = false;
436 	archive_num num_max = 0;
437 	datetime last_mtime = datetime(0); // used as deleted_date for EA if last mtime is found
438 	bool found_in_archive = false;
439 
440 	    // checking the last_mod map
441 
442 	while(it != last_mod.end() && !found_in_archive)
443 	{
444 	    if(it->first == archive && it->second.present != et_absent)
445 		found_in_archive = true;
446 	    else
447 		if(it->first > num_max && (it->first < ignore_archives_greater_or_equal || ignore_archives_greater_or_equal == 0))
448 		{
449 		    num_max = it->first;
450 		    switch(it->second.present)
451 		    {
452 		    case et_saved:
453 		    case et_present:
454 			presence_max = true;
455 			last_mtime = it->second.date; // used as deleted_data for EA
456 			break;
457 		    case et_removed:
458 		    case et_absent:
459 			presence_max = false;
460 			last_mtime = it->second.date; // keeping this date as it is
461 			    // not possible to know when the EA have been removed
462 			    // since it does not change mtime of file nor of its parent
463 			    // directory (this is an heuristic).
464 			break;
465 		    default:
466 			throw SRC_BUG;
467 		    }
468 		}
469 	    ++it;
470 	}
471 
472 	if(!found_in_archive) // not entry found for asked archive (or recorded as et_absent)
473 	{
474 	    if(presence_max)
475 	    {
476 		    // the most recent entry found stated that this file
477 		    // existed at the time "last_mtime"
478 		    // and this entry is absent from the archive under addition
479 		    // thus we must record that it has been removed in the
480 		    // archive we currently add.
481 
482 		if(deleted_date > last_mtime)
483 		    set_data(archive, deleted_date, et_absent);
484 		    // add an entry telling that this file does no more exist in the current archive
485 		else
486 		    set_data(archive, last_mtime, et_absent);
487 		    // add an entry telling thatthis file does no more exists, using the last known date
488 		    // as deleted data. This situation may appear when one makes a first backup
489 		    // then a second one but excluding from the backup that particular file. This file
490 		    // may reappear later with if is backup included in the backup possibily with the same
491 		    // date. In 2.4.x we added 1 second to the last known date to create the deleted date
492 		    // which lead out of order warning to show for the database. Since 2.5.x date resolution
493 		    // may be one microsecond (no more 1 second) thus we now keep the last known date as
494 		    // delete date
495 	    }
496 	    else // the entry has been seen previously but as removed in the latest state,
497 		    // if we already have an et_absent entry (which is possible during a reordering archive operation within a database), we can (and must) suppress it.
498 	    {
499 		map<archive_num, status>::iterator it = last_mod.find(archive);
500 		if(it != last_mod.end()) // we have an entry associated to this archive
501 		{
502 		    switch(it->second.present)
503 		    {
504 		    case et_saved:
505 		    case et_present:
506 			throw SRC_BUG; // entry has not been found in the current archive
507 		    case et_removed:
508 			break;         // we must keep it, it was in the original archive
509 		    case et_absent:
510 			last_mod.erase(it); // this entry had been added from previous neighbor archive, we must remove it now, it was not part of the original archive
511 			break;
512 		    default:
513 			throw SRC_BUG;
514 		    }
515 		}
516 		    // else already absent from the base for this archive, cool.
517 	    }
518 	}
519 	    // else, entry found for the current archive
520 
521 
522 
523 	    //////////////////////
524 	    // now checking the last_change map
525 	    //
526 
527 
528 	it = last_change.begin();
529 	presence_max = false;
530 	num_max = 0;
531 	found_in_archive = false;
532 
533 	while(it != last_change.end() && !found_in_archive)
534 	{
535 	    if(it->first == archive && it->second.present != et_absent)
536 		found_in_archive = true;
537 	    else
538 		if(it->first > num_max && (it->first < ignore_archives_greater_or_equal || ignore_archives_greater_or_equal == 0))
539 		{
540 		    num_max = it->first;
541 		    switch(it->second.present)
542 		    {
543 		    case et_saved:
544 		    case et_present:
545 			presence_max = true;
546 			break;
547 		    case et_removed:
548 		    case et_absent:
549 			presence_max = false;
550 			break;
551 		    default:
552 			throw SRC_BUG;
553 		    }
554 		}
555 	    ++it;
556 	}
557 
558 	if(!found_in_archive) // not entry found for asked archive
559 	{
560 	    if(num_max != 0) // num_max may be equal to zero if this entry never had any EA in any recorded archive
561 		if(presence_max)
562 		    set_EA(archive, (last_mtime < deleted_date ? deleted_date : last_mtime), et_absent); // add an entry telling that EA for this file do no more exist in the current archive
563 		// else last entry found stated the EA was removed, nothing more to do
564 	}
565 	    // else, entry found for the current archive
566     }
567 
remove_all_from(const archive_num & archive_to_remove,const archive_num & last_archive)568     bool data_tree::remove_all_from(const archive_num & archive_to_remove, const archive_num & last_archive)
569     {
570 	map<archive_num, status>::iterator it = last_mod.begin();
571 
572 
573 	    // if this file was stored as "removed" in the archive we tend to remove from the database
574 	    // this "removed" information is propagated to the next archive if both:
575 	    //   - the next archive exists and has no information recorded about this file
576 	    //   - this entry does not only exist in the archive about to be removed
577 	if(archive_to_remove < last_archive)
578 	{
579 	    datetime del_date;
580 	    etat status;
581 	    if(last_mod.size() > 1 && read_data(archive_to_remove, del_date, status))
582 		if(status == et_removed)
583 		{
584 		    datetime tmp;
585 		    if(!read_data(archive_to_remove + 1, tmp, status))
586 			set_data(archive_to_remove + 1, del_date, et_removed);
587 		}
588 	    if(last_change.size() > 1 && read_EA(archive_to_remove, del_date, status))
589 		if(status == et_removed)
590 		{
591 		    datetime tmp;
592 		    if(!read_EA(archive_to_remove + 1, tmp, status))
593 			set_EA(archive_to_remove + 1, del_date, et_removed);
594 		}
595 	}
596 
597 	while(it != last_mod.end())
598 	{
599 	    if(it->first == archive_to_remove)
600 	    {
601 		last_mod.erase(it);
602 		break; // stops the while loop, as there is at most one element with that key
603 	    }
604 	    else
605 		++it;
606 	}
607 
608 	it = last_change.begin();
609 	while(it != last_change.end())
610 	{
611 	    if(it->first == archive_to_remove)
612 	    {
613 		last_change.erase(it);
614 		break; // stops the while loop, as there is at most one element with that key
615 	    }
616 	    else
617 		++it;
618 	}
619 
620 	return last_mod.empty() && last_change.empty();
621     }
622 
listing(user_interaction & dialog) const623     void data_tree::listing(user_interaction & dialog) const
624     {
625 	map<archive_num, status>::const_iterator it = last_mod.begin();
626 	map<archive_num, status>::const_iterator ut = last_change.begin();
627 
628 	dialog.printf(gettext("Archive number |  Data                   | status ||  EA                     | status \n"));
629 	dialog.printf(gettext("---------------+-------------------------+--------++-------------------------+----------\n"));
630 
631 	while(it != last_mod.end() || ut != last_change.end())
632 	{
633 	    if(it != last_mod.end())
634 		if(ut != last_change.end())
635 		    if(it->first == ut->first)
636 		    {
637 			display_line(dialog, it->first, &(it->second.date), it->second.present, &(ut->second.date), ut->second.present);
638 			++it;
639 			++ut;
640 		    }
641 		    else // not in the same archive
642 			if(it->first < ut->first) // it only
643 			{
644 			    display_line(dialog, it->first, &(it->second.date), it->second.present, nullptr, data_tree::et_removed);
645 			    ++it;
646 			}
647 			else // ut only
648 			{
649 			    display_line(dialog, ut->first, nullptr, data_tree::et_removed, &(ut->second.date), ut->second.present);
650 			    ++ut;
651 			}
652 		else // ut at end of list thus it != last_mod.end() (see while condition)
653 		{
654 		    display_line(dialog, it->first, &(it->second.date), it->second.present, nullptr, data_tree::et_removed);
655 		    ++it;
656 		}
657 	    else // it at end of list, this ut != last_change.end() (see while condition)
658 	    {
659 		display_line(dialog, ut->first, nullptr, data_tree::et_removed, &(ut->second.date), ut->second.present);
660 		++ut;
661 	    }
662 	}
663     }
664 
apply_permutation(archive_num src,archive_num dst)665     void data_tree::apply_permutation(archive_num src, archive_num dst)
666     {
667 	map<archive_num, status> transfert;
668 	map<archive_num, status>::iterator it = last_mod.begin();
669 
670 	transfert.clear();
671 	while(it != last_mod.end())
672 	{
673 	    transfert[data_tree_permutation(src, dst, it->first)] = it->second;
674 	    ++it;
675 	}
676 	last_mod = transfert;
677 
678 	transfert.clear();
679 	it = last_change.begin();
680 	while(it != last_change.end())
681 	{
682 	    transfert[data_tree_permutation(src, dst, it->first)] = it->second;
683 	    ++it;
684 	}
685 	last_change = transfert;
686     }
687 
skip_out(archive_num num)688     void data_tree::skip_out(archive_num num)
689     {
690 	map<archive_num, status> resultant;
691 	map<archive_num, status>::iterator it = last_mod.begin();
692 	infinint tmp;
693 
694 	while(it != last_mod.end())
695 	{
696 	    if(it->first > num)
697 		resultant[it->first-1] = it->second;
698 	    else
699 		resultant[it->first] = it->second;
700 	    ++it;
701 	}
702 	last_mod = resultant;
703 
704 	resultant.clear();
705 	it = last_change.begin();
706 	while(it != last_change.end())
707 	{
708 	    if(it->first > num)
709 		resultant[it->first-1] = it->second;
710 	    else
711 		resultant[it->first] = it->second;
712 	    ++it;
713 	}
714 	last_change = resultant;
715     }
716 
compute_most_recent_stats(vector<infinint> & data,vector<infinint> & ea,vector<infinint> & total_data,vector<infinint> & total_ea) const717     void data_tree::compute_most_recent_stats(vector<infinint> & data, vector<infinint> & ea,
718 					      vector<infinint> & total_data, vector<infinint> & total_ea) const
719     {
720 	archive_num most_recent = 0;
721 	datetime max = datetime(0);
722 	map<archive_num, status>::const_iterator it = last_mod.begin();
723 
724 	while(it != last_mod.end())
725 	{
726 	    if(it->second.present == et_saved)
727 	    {
728 		if(it->second.date >= max)
729 		{
730 		    most_recent = it->first;
731 		    max = it->second.date;
732 		}
733 		++total_data[it->first];
734 	    }
735 	    ++it;
736 	}
737 	if(most_recent > 0)
738 	    ++data[most_recent];
739 
740 	most_recent = 0;
741 	max = datetime(0);
742 	it = last_change.begin();
743 
744 	while(it != last_change.end())
745 	{
746 	    if(it->second.present == et_saved)
747 	    {
748 		if(it->second.date >= max)
749 		{
750 		    most_recent = it->first;
751 		    max = it->second.date;
752 		}
753 		++total_ea[it->first];
754 	    }
755 	    ++it;
756 	}
757 	if(most_recent > 0)
758 	    ++ea[most_recent];
759     }
760 
761 	// helper data structure for check_map_order method
762 
763     struct trecord
764     {
765 	datetime date;
766 	bool set;
767 
trecordlibdar::trecord768 	trecord() { set = false; date = datetime(0); };
trecordlibdar::trecord769 	trecord(const trecord & ref) { date = ref.date; set = ref.set; };
operator =libdar::trecord770 	const trecord & operator = (const trecord & ref) { date = ref.date; set = ref.set; return *this; };
771     };
772 
773 
fix_corruption()774     bool data_tree::fix_corruption()
775     {
776 	bool ret = true;
777 	map<archive_num, status>::iterator it = last_mod.begin();
778 
779 	while(it != last_mod.end() && ret)
780 	{
781 	    if(it->second.present != et_removed && it->second.present != et_absent)
782 		ret = false;
783 	    ++it;
784 	}
785 
786         it = last_change.begin();
787 	while(it != last_change.end() && ret)
788 	{
789 	    if(it->second.present != et_removed && it->second.present != et_absent)
790 		ret = false;
791 	    ++it;
792 	}
793 
794 	return ret;
795     }
796 
check_map_order(user_interaction & dialog,const map<archive_num,status> the_map,const path & current_path,const string & field_nature,bool & initial_warn) const797     bool data_tree::check_map_order(user_interaction & dialog,
798 				    const map<archive_num, status> the_map,
799 				    const path & current_path,
800 				    const string & field_nature,
801 				    bool & initial_warn) const
802     {
803 
804 
805 	    // some variable to work with around the_map
806 
807 	U_I dates_size = the_map.size()+1;
808 	vector<trecord> dates = vector<trecord>(dates_size);
809 	map<archive_num, status>::const_iterator it = the_map.begin();
810 	vector<trecord>::iterator rec_it;
811 	datetime last_date = datetime(0);
812 
813 	    // now the algorithm
814 
815 	    // extracting dates from map into an ordered vector
816 
817 	while(it != the_map.end())
818 	{
819 	    trecord rec;
820 
821 	    rec.date = it->second.date;
822 	    rec.set = true;
823 	    while(dates_size <= it->first)
824 	    {
825 		dates.push_back(trecord());
826 		++dates_size;
827 	    }
828 	    dates[it->first] = rec;
829 
830 	    ++it;
831 	}
832 
833 	    // checking whether dates are sorted crescendo following the archive number
834 
835 	rec_it = dates.begin();
836 
837 	while(rec_it != dates.end())
838 	{
839 	    if(rec_it->set)
840 	    {
841 		if(rec_it->date >= last_date)
842 		    last_date = rec_it->date;
843 		else // order is not respected
844 		{
845 		    string tmp = current_path.display() == "." ? get_name() : (current_path + get_name()).display();
846 		    dialog.printf(gettext("Dates of file's %S are not increasing when database's archive number grows. Concerned file is: %S"), &field_nature, &tmp);
847 		    if(initial_warn)
848 		    {
849 			dialog.warning(gettext("Dates are not increasing for all files when database's archive number grows, working with this database may lead to improper file's restored version. Please reorder the archive within the database in the way that the older is the first archive and so on up to the most recent archive being the last of the database"));
850 			try
851 			{
852 			    dialog.pause(gettext("Do you want to ignore the same type of error for other files?"));
853 			    return false;
854 			}
855 			catch(Euser_abort & e)
856 			{
857 			    initial_warn = false;
858 			}
859 		    }
860 		    break; // aborting the while loop
861 		}
862 	    }
863 	    ++rec_it;
864 	}
865 
866 	return true;
867     }
868 
869 
870 ////////////////////////////////////////////////////////////////
871 
data_dir(const string & name)872     data_dir::data_dir(const string &name) : data_tree(name)
873     {
874 	rejetons.clear();
875     }
876 
data_dir(generic_file & f,unsigned char db_version)877     data_dir::data_dir(generic_file &f, unsigned char db_version) : data_tree(f, db_version)
878     {
879 	infinint tmp = infinint(f); // number of children
880 	data_tree *entry = nullptr;
881 	rejetons.clear();
882 
883 	try
884 	{
885 	    while(!tmp.is_zero())
886 	    {
887 		entry = read_from_file(f, db_version, get_pool());
888 		if(entry == nullptr)
889 		    throw Erange("data_dir::data_dir", gettext("Unexpected end of file"));
890 		rejetons.push_back(entry);
891 		entry = nullptr;
892 		--tmp;
893 	    }
894 	}
895 	catch(...)
896 	{
897 	    list<data_tree *>::iterator next = rejetons.begin();
898 	    while(next != rejetons.end())
899 	    {
900 		delete *next;
901 		*next = nullptr;
902 		++next;
903 	    }
904 	    if(entry != nullptr)
905 		delete entry;
906 	    throw;
907 	}
908     }
909 
data_dir(const data_dir & ref)910     data_dir::data_dir(const data_dir & ref) : data_tree(ref)
911     {
912 	rejetons.clear();
913     }
914 
data_dir(const data_tree & ref)915     data_dir::data_dir(const data_tree & ref) : data_tree(ref)
916     {
917 	rejetons.clear();
918     }
919 
~data_dir()920     data_dir::~data_dir()
921     {
922 	list<data_tree *>::iterator next = rejetons.begin();
923 	while(next != rejetons.end())
924 	{
925 	    delete *next;
926 	    *next = nullptr;
927 	    ++next;
928 	}
929     }
930 
dump(generic_file & f) const931     void data_dir::dump(generic_file & f) const
932     {
933 	list<data_tree *>::const_iterator it = rejetons.begin();
934 	infinint tmp = rejetons.size();
935 
936 	data_tree::dump(f);
937 	tmp.dump(f);
938 	while(it != rejetons.end())
939 	{
940 	    if(*it == nullptr)
941 		throw SRC_BUG;
942 	    (*it)->dump(f);
943 	    ++it;
944 	}
945     }
946 
947 
find_or_addition(const string & name,bool is_dir,const archive_num & archive)948     data_tree *data_dir::find_or_addition(const string & name, bool is_dir, const archive_num & archive)
949     {
950 	const data_tree *fils = read_child(name);
951 	data_tree *ret = nullptr;
952 
953 	if(fils == nullptr) // brand-new data_tree to build
954 	{
955 	    if(is_dir)
956 		ret = new (get_pool()) data_dir(name);
957 	    else
958 		ret = new (get_pool()) data_tree(name);
959 	    if(ret == nullptr)
960 		throw Ememory("data_dir::find_or_addition");
961 	    add_child(ret);
962 	}
963 	else  // already saved in another archive
964 	{
965 		// check if dir/file nature has changed
966 	    const data_dir *fils_dir = dynamic_cast<const data_dir *>(fils);
967 	    if(fils_dir == nullptr && is_dir) // need to upgrade data_tree to data_dir
968 	    {
969 		ret = new (get_pool()) data_dir(*fils); // upgrade data_tree in an empty data_dir
970 		if(ret == nullptr)
971 		    throw Ememory("data_dir::find_or_addition");
972 		try
973 		{
974 		    remove_child(name);
975 		    add_child(ret);
976 		}
977 		catch(...)
978 		{
979 		    delete ret;
980 		    throw;
981 		}
982 	    }
983 	    else // no change in dir/file nature
984 		ret = const_cast<data_tree *>(fils);
985 	}
986 
987 	return ret;
988     }
989 
add(const cat_inode * entry,const archive_num & archive)990     void data_dir::add(const cat_inode *entry, const archive_num & archive)
991     {
992 	const cat_directory *entry_dir = dynamic_cast<const cat_directory *>(entry);
993 	data_tree * tree = find_or_addition(entry->get_name(), entry_dir != nullptr, archive);
994 	archive_num last_archive;
995 	lookup result;
996 	datetime last_mod = entry->get_last_modif() > entry->get_last_change() ? entry->get_last_modif() : entry->get_last_change();
997 
998 	switch(entry->get_saved_status())
999 	{
1000 	case s_saved:
1001 	case s_fake:
1002 	    tree->set_data(archive, last_mod, et_saved);
1003 	    break;
1004 	case s_not_saved:
1005 	    tree->set_data(archive, last_mod, et_present);
1006 	    break;
1007 	default:
1008 	    throw SRC_BUG;
1009 	}
1010 
1011 	switch(entry->ea_get_saved_status())
1012 	{
1013 	case cat_inode::ea_none:
1014 	    break;
1015 	case cat_inode::ea_removed:
1016 	    result = tree->get_EA(last_archive, datetime(0), false);
1017 	    if(result == found_present || result == not_restorable)
1018 		tree->set_EA(archive, entry->get_last_change(), et_removed);
1019 		// else no need to add an et_remove entry in the map
1020 	    break;
1021 	case cat_inode::ea_partial:
1022 	    tree->set_EA(archive, entry->get_last_change(), et_present);
1023 	    break;
1024 	case cat_inode::ea_fake:
1025 	case cat_inode::ea_full:
1026 	    tree->set_EA(archive, entry->get_last_change(), et_saved);
1027 	    break;
1028 	default:
1029 	    throw SRC_BUG;
1030 	}
1031     }
1032 
add(const cat_detruit * entry,const archive_num & archive)1033     void data_dir::add(const cat_detruit *entry, const archive_num & archive)
1034     {
1035 	data_tree * tree = find_or_addition(entry->get_name(), false, archive);
1036 	archive_num last_archive;
1037 	lookup result;
1038 
1039 	result = tree->get_data(last_archive, datetime(0), false);
1040 	if(result == found_present || result == not_restorable)
1041 	    tree->set_data(archive, entry->get_date(), et_removed);
1042 	result = tree->get_EA(last_archive, datetime(0), false);
1043 	if(result == found_present || result == not_restorable)
1044 	    tree->set_EA(archive, entry->get_date(), et_removed);
1045     }
1046 
read_child(const string & name) const1047     const data_tree *data_dir::read_child(const string & name) const
1048     {
1049 	list<data_tree *>::const_iterator it = rejetons.begin();
1050 
1051 	while(it != rejetons.end() && *it != nullptr && (*it)->get_name() != name)
1052 	    ++it;
1053 
1054 	if(it == rejetons.end())
1055 	    return nullptr;
1056 	else
1057 	    if(*it == nullptr)
1058 		throw SRC_BUG;
1059 	    else
1060 		return *it;
1061     }
1062 
read_all_children(vector<string> & fils) const1063     void data_dir::read_all_children(vector<string> & fils) const
1064     {
1065 	list<data_tree *>::const_iterator it = rejetons.begin();
1066 
1067 	fils.clear();
1068 	while(it != rejetons.end())
1069 	    fils.push_back((*it++)->get_name());
1070     }
1071 
check_order(user_interaction & dialog,const path & current_path,bool & initial_warn) const1072     bool data_dir::check_order(user_interaction & dialog, const path & current_path, bool & initial_warn) const
1073     {
1074 	list<data_tree *>::const_iterator it = rejetons.begin();
1075 	bool ret = data_tree::check_order(dialog, current_path, initial_warn);
1076 	path subpath = current_path.display() == "." ? get_name() : current_path + get_name();
1077 
1078 	while(it != rejetons.end() && ret)
1079 	{
1080 	    if(*it == nullptr)
1081 		throw SRC_BUG;
1082 	    ret = (*it)->check_order(dialog, subpath, initial_warn);
1083 	    ++it;
1084 	}
1085 
1086 	return ret;
1087     }
1088 
finalize(const archive_num & archive,const datetime & deleted_date,const archive_num & ignore_archives_greater_or_equal)1089     void data_dir::finalize(const archive_num & archive, const datetime & deleted_date, const archive_num & ignore_archives_greater_or_equal)
1090     {
1091 	datetime new_deleted_date;
1092 	archive_num tmp_archive;
1093 	etat tmp_presence;
1094 
1095 	data_tree::finalize(archive, deleted_date, ignore_archives_greater_or_equal);
1096 
1097 	switch(get_data(tmp_archive, datetime(0), false))
1098 	{
1099 	case found_present:
1100 	case found_removed:
1101 	    break; // acceptable result
1102 	case not_found:
1103 	    if(fix_corruption())
1104 		throw Edata("This is to signal the caller of this method that this object has to be removed from database"); // exception caugth in data_dir::finalize_except_self
1105 	    throw Erange("data_dir::finalize", gettext("This database has been corrupted probably due to a bug in release 2.4.0 to 2.4.9, and it has not been possible to cleanup this corruption, please rebuild the database from archives or extracted \"catalogues\", if the database has never been used by one of the previously mentioned released, you are welcome to open a bug report and provide as much as possible details about the circumstances"));
1106 	case not_restorable:
1107 	    break;  // also an acceptable result;
1108 	default:
1109 	    throw SRC_BUG;
1110 	}
1111 
1112 	if(!read_data(tmp_archive, new_deleted_date, tmp_presence))
1113 	    throw SRC_BUG;
1114 
1115 	finalize_except_self(archive, new_deleted_date, ignore_archives_greater_or_equal);
1116     }
1117 
finalize_except_self(const archive_num & archive,const datetime & deleted_date,const archive_num & ignore_archives_greater_or_equal)1118     void data_dir::finalize_except_self(const archive_num & archive, const datetime & deleted_date, const archive_num & ignore_archives_greater_or_equal)
1119     {
1120 	list<data_tree *>::iterator it = rejetons.begin();
1121 
1122 	while(it != rejetons.end())
1123 	{
1124 	    if(*it == nullptr)
1125 		throw SRC_BUG;
1126 	    try
1127 	    {
1128 		(*it)->finalize(archive, deleted_date, ignore_archives_greater_or_equal);
1129 		++it;
1130 	    }
1131 	    catch(Edata & e)
1132 	    {
1133 		delete (*it);
1134 		rejetons.erase(it);
1135 		it = rejetons.begin();
1136 	    }
1137 	}
1138     }
1139 
1140 
remove_all_from(const archive_num & archive_to_remove,const archive_num & last_archive)1141     bool data_dir::remove_all_from(const archive_num & archive_to_remove, const archive_num & last_archive)
1142     {
1143 	list<data_tree *>::iterator it = rejetons.begin();
1144 
1145 	while(it != rejetons.end())
1146 	{
1147 	    if((*it) == nullptr)
1148 		throw SRC_BUG;
1149 	    if((*it)->remove_all_from(archive_to_remove, last_archive))
1150 	    {
1151 		delete *it; // release the memory used by the object
1152 		*it = nullptr;
1153 		rejetons.erase(it); // remove the entry from the list
1154 		it = rejetons.begin(); // does not seems "it" points to the next item after erase, so we restart from the beginning
1155 	    }
1156 	    else
1157 		++it;
1158 	}
1159 
1160 	return data_tree::remove_all_from(archive_to_remove, last_archive) && rejetons.size() == 0;
1161     }
1162 
show(user_interaction & dialog,archive_num num,string marge) const1163     void data_dir::show(user_interaction & dialog, archive_num num, string marge) const
1164     {
1165 	list<data_tree *>::const_iterator it = rejetons.begin();
1166 	archive_num ou_data, ou_ea;
1167 	bool data, ea;
1168 	string etat, name;
1169 	lookup lo_data, lo_ea;
1170 	bool even_when_removed = (num != 0);
1171 
1172 	while(it != rejetons.end())
1173 	{
1174 	    if(*it == nullptr)
1175 		throw SRC_BUG;
1176 	    data_dir *dir = dynamic_cast<data_dir *>(*it);
1177 
1178 	    lo_data = (*it)->get_data(ou_data, datetime(0), even_when_removed);
1179 	    lo_ea = (*it)->get_EA(ou_ea, datetime(0), even_when_removed);
1180 	    data = lo_data == found_present && (ou_data == num || num == 0);
1181 	    ea = lo_ea == found_present && (ou_ea == num || num == 0);
1182 	    name = (*it)->get_name();
1183 	    if(data || ea || num == 0)
1184 	    {
1185 		etat = "";
1186 		if(data)
1187 		    etat += gettext("[ Saved ]");
1188 		else
1189 		    etat += gettext("[       ]");
1190 
1191 		if(ea)
1192 		    etat += gettext("[  EA   ]");
1193 		else
1194 		    etat += gettext("[       ]");
1195 
1196 		if(dialog.get_use_dar_manager_show_files())
1197 		    dialog.dar_manager_show_files(name, data, ea);
1198 		else
1199 		    dialog.printf("%S  %S%S\n", &etat, &marge, &name);
1200 	    }
1201 	    if(dir != nullptr)
1202 		dir->show(dialog, num, marge+name+"/");
1203 	    ++it;
1204 	}
1205     }
1206 
apply_permutation(archive_num src,archive_num dst)1207     void data_dir::apply_permutation(archive_num src, archive_num dst)
1208     {
1209 	list<data_tree *>::iterator it = rejetons.begin();
1210 
1211 	data_tree::apply_permutation(src, dst);
1212 	while(it != rejetons.end())
1213 	{
1214 	    (*it)->apply_permutation(src, dst);
1215 	    ++it;
1216 	}
1217     }
1218 
1219 
skip_out(archive_num num)1220     void data_dir::skip_out(archive_num num)
1221     {
1222 	list<data_tree *>::iterator it = rejetons.begin();
1223 
1224 	data_tree::skip_out(num);
1225 	while(it != rejetons.end())
1226 	{
1227 	    (*it)->skip_out(num);
1228 	    ++it;
1229 	}
1230     }
1231 
compute_most_recent_stats(vector<infinint> & data,vector<infinint> & ea,vector<infinint> & total_data,vector<infinint> & total_ea) const1232     void data_dir::compute_most_recent_stats(vector<infinint> & data, vector<infinint> & ea,
1233 					     vector<infinint> & total_data, vector<infinint> & total_ea) const
1234     {
1235 	list<data_tree *>::const_iterator it = rejetons.begin();
1236 
1237 	data_tree::compute_most_recent_stats(data, ea, total_data, total_ea);
1238 	while(it != rejetons.end())
1239 	{
1240 	    (*it)->compute_most_recent_stats(data, ea, total_data, total_ea);
1241 	    ++it;
1242 	}
1243     }
1244 
fix_corruption()1245     bool data_dir::fix_corruption()
1246     {
1247 	while(rejetons.begin() != rejetons.end() && *(rejetons.begin()) != nullptr && (*(rejetons.begin()))->fix_corruption())
1248 	{
1249 	    delete *(rejetons.begin());
1250 	    rejetons.erase(rejetons.begin());
1251 	}
1252 
1253 	if(rejetons.begin() != rejetons.end())
1254 	    return false;
1255 	else
1256 	    return data_tree::fix_corruption();
1257 
1258     }
1259 
add_child(data_tree * fils)1260     void data_dir::add_child(data_tree *fils)
1261     {
1262 	if(fils == nullptr)
1263 	    throw SRC_BUG;
1264 	rejetons.push_back(fils);
1265     }
1266 
remove_child(const string & name)1267     void data_dir::remove_child(const string & name)
1268     {
1269 	list<data_tree *>::iterator it = rejetons.begin();
1270 
1271 	while(it != rejetons.end() && *it != nullptr && (*it)->get_name() != name)
1272 	    ++it;
1273 
1274 	if(it != rejetons.end())
1275 	{
1276 	    if(*it == nullptr)
1277 		throw SRC_BUG;
1278 	    else
1279 		rejetons.erase(it);
1280 	}
1281     }
1282 
1283 
1284 ////////////////////////////////////////////////////////////////
1285 
data_tree_read(generic_file & f,unsigned char db_version,memory_pool * pool)1286     data_dir *data_tree_read(generic_file & f, unsigned char db_version, memory_pool *pool)
1287     {
1288 	data_tree *lu = read_from_file(f, db_version, pool);
1289 	data_dir *ret = dynamic_cast<data_dir *>(lu);
1290 
1291 	if(ret == nullptr && lu != nullptr)
1292 	    delete lu;
1293 
1294 	return ret;
1295     }
1296 
data_tree_find(path chemin,const data_dir & racine,const data_tree * & ptr)1297     bool data_tree_find(path chemin, const data_dir & racine, const data_tree *& ptr)
1298     {
1299 	string filename;
1300 	const data_dir *current = &racine;
1301 	bool loop = true;
1302 
1303 	if(!chemin.is_relative())
1304 	    throw SRC_BUG;
1305 
1306 	while(loop)
1307 	{
1308 	    if(!chemin.pop_front(filename))
1309 	    {
1310 		filename = chemin.display();
1311 		loop = false;
1312 	    }
1313 	    ptr = current->read_child(filename);
1314 	    if(ptr == nullptr)
1315 		loop = false;
1316 	    if(loop)
1317 	    {
1318 		current = dynamic_cast<const data_dir *>(ptr);
1319 		if(current == nullptr)
1320 		{
1321 		    loop = false;
1322 		    ptr = nullptr;
1323 		}
1324 	    }
1325 	}
1326 
1327 	return ptr != nullptr;
1328     }
1329 
data_tree_update_with(const cat_directory * dir,archive_num archive,data_dir * racine)1330     void data_tree_update_with(const cat_directory *dir, archive_num archive, data_dir *racine)
1331     {
1332 	const cat_nomme *entry;
1333 
1334 	dir->reset_read_children();
1335 	while(dir->read_children(entry))
1336 	{
1337 	    const cat_directory *entry_dir = dynamic_cast<const cat_directory *>(entry);
1338 	    const cat_inode *entry_ino = dynamic_cast<const cat_inode *>(entry);
1339 	    const cat_mirage *entry_mir = dynamic_cast<const cat_mirage *>(entry);
1340 	    const cat_detruit *entry_det = dynamic_cast<const cat_detruit *>(entry);
1341 
1342 	    if(entry_mir != nullptr)
1343 	    {
1344 		entry_ino = entry_mir->get_inode();
1345 		entry_mir->get_inode()->change_name(entry_mir->get_name());
1346 	    }
1347 
1348 	    if(entry_ino == nullptr)
1349 		if(entry_det != nullptr)
1350 		{
1351 		    if(!entry_det->get_date().is_null())
1352 			racine->add(entry_det, archive);
1353 			// else this is an old archive that does not store date with cat_detruit objects
1354 		}
1355 		else
1356 		    continue; // continue with next loop, we ignore entree objects that are neither inode nor cat_detruit
1357 	    else
1358 		racine->add(entry_ino, archive);
1359 
1360 	    if(entry_dir != nullptr) // going into recursion
1361 	    {
1362 		data_tree *new_root = const_cast<data_tree *>(racine->read_child(entry->get_name()));
1363 		data_dir *new_root_dir = dynamic_cast<data_dir *>(new_root);
1364 
1365 		if(new_root == nullptr)
1366 		    throw SRC_BUG; // the racine->add method did not add an item for "entry"
1367 		if(new_root_dir == nullptr)
1368 		    throw SRC_BUG; // the racine->add method did not add a data_dir item
1369 		data_tree_update_with(entry_dir, archive, new_root_dir);
1370 	    }
1371 	}
1372     }
1373 
data_tree_permutation(archive_num src,archive_num dst,archive_num x)1374     archive_num data_tree_permutation(archive_num src, archive_num dst, archive_num x)
1375     {
1376 	if(src < dst)
1377 	    if(x < src || x > dst)
1378 		return x;
1379 	    else
1380 		if(x == src)
1381 		    return dst;
1382 		else
1383 		    return x-1;
1384 	else
1385 	    if(src == dst)
1386 		return x;
1387 	    else // src > dst
1388 		if(x > src || x < dst)
1389 		    return x;
1390 		else
1391 		    if(x == src)
1392 			return dst;
1393 		    else
1394 			return x+1;
1395     }
1396 
1397 } // end of namespace
1398 
1399 
1400 ////////////////////////////////////////////////////////////////
1401 
read_from_file(generic_file & f,unsigned char db_version,memory_pool * pool)1402 static data_tree *read_from_file(generic_file & f, unsigned char db_version, memory_pool *pool)
1403 {
1404     char sign;
1405     data_tree *ret;
1406 
1407     if(f.read(&sign, 1) != 1)
1408         return nullptr; // nothing more to read
1409 
1410     if(sign == data_tree::signature())
1411         ret = new (pool) data_tree(f, db_version);
1412     else if(sign == data_dir::signature())
1413         ret = new (pool) data_dir(f, db_version);
1414     else
1415         throw Erange("read_from_file", gettext("Unknown record type"));
1416 
1417     if(ret == nullptr)
1418         throw Ememory("read_from_file");
1419 
1420     return ret;
1421 }
1422 
read_from_file(generic_file & f,archive_num & a)1423 static void read_from_file(generic_file &f, archive_num &a)
1424 {
1425     char buffer[sizeof(archive_num)];
1426     archive_num *ptr = (archive_num *)&(buffer[0]);
1427 
1428     f.read(buffer, sizeof(archive_num));
1429     a = ntohs(*ptr);
1430 }
1431 
write_to_file(generic_file & f,archive_num a)1432 static void write_to_file(generic_file &f, archive_num a)
1433 {
1434     char buffer[sizeof(archive_num)];
1435     archive_num *ptr = (archive_num *)&(buffer[0]);
1436 
1437     *ptr = htons(a);
1438     f.write(buffer, sizeof(archive_num));
1439 }
1440 
display_line(user_interaction & dialog,archive_num num,const datetime * data,data_tree::etat data_presence,const datetime * ea,data_tree::etat ea_presence)1441 static void display_line(user_interaction & dialog,
1442 			 archive_num num,
1443 			 const datetime *data,
1444 			 data_tree::etat data_presence,
1445 			 const datetime *ea,
1446 			 data_tree::etat ea_presence)
1447 {
1448 
1449     const string REMOVED = gettext("removed ");
1450     const string PRESENT = gettext("present ");
1451     const string SAVED   = gettext("saved   ");
1452     const string ABSENT  = gettext("absent  ");
1453     const string NO_DATE = "                          ";
1454 
1455     string data_state;
1456     string ea_state;
1457     string data_date;
1458     string ea_date;
1459 
1460     switch(data_presence)
1461     {
1462     case data_tree::et_saved:
1463 	data_state = SAVED;
1464 	break;
1465     case data_tree::et_present:
1466 	data_state = PRESENT;
1467 	break;
1468     case data_tree::et_removed:
1469 	data_state = REMOVED;
1470 	break;
1471     case data_tree::et_absent:
1472 	data_state = ABSENT;
1473 	break;
1474     default:
1475 	throw SRC_BUG;
1476     }
1477 
1478     switch(ea_presence)
1479     {
1480     case data_tree::et_saved:
1481 	ea_state = SAVED;
1482 	break;
1483     case data_tree::et_present:
1484 	ea_state = PRESENT;
1485 	break;
1486     case data_tree::et_removed:
1487 	ea_state = REMOVED;
1488 	break;
1489     case data_tree::et_absent:
1490 	throw SRC_BUG; // state not used for EA
1491     default:
1492 	throw SRC_BUG;
1493     }
1494 
1495     if(data == nullptr)
1496     {
1497 	data_state = ABSENT;
1498 	data_date = NO_DATE;
1499     }
1500     else
1501 	data_date = tools_display_date(*data);
1502 
1503     if(ea == nullptr)
1504     {
1505 	ea_state = ABSENT;
1506 	ea_date = NO_DATE;
1507     }
1508     else
1509 	ea_date = tools_display_date(*ea);
1510 
1511     if(dialog.get_use_dar_manager_show_version())
1512 	dialog.dar_manager_show_version(num, data_date, data_state, ea_date, ea_state);
1513     else
1514 	dialog.printf(" \t%u\t%S  %S  %S  %S\n", num, &data_date, &data_state, &ea_date, &ea_state);
1515 }
1516