1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2002-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2016-2016 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation and included
10    in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    Affero General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /*
23  * Kern Sibbald, October MMII
24  */
25 /**
26  * @file
27  * Encode and decode Extended attributes for Win32 and
28  * other non-Unix systems, or Unix systems with ACLs, ...
29  */
30 
31 #include "include/bareos.h"
32 #include "include/jcr.h"
33 #include "find.h"
34 #include "include/ch.h"
35 #include "findlib/attribs.h"
36 #include "lib/edit.h"
37 #include "lib/berrno.h"
38 
39 static uid_t my_uid = 1;
40 static gid_t my_gid = 1;
41 static bool uid_set = false;
42 
43 #if defined(HAVE_WIN32)
44 
45 /* Imported Functions */
46 extern void unix_name_to_win32(POOLMEM*& win32_name, const char* name);
47 
48 /* Forward referenced subroutines */
49 static bool set_win32_attributes(JobControlRecord* jcr,
50                                  Attributes* attr,
51                                  BareosWinFilePacket* ofd);
52 void WinError(JobControlRecord* jcr, const char* prefix, POOLMEM* ofile);
53 #endif /* HAVE_WIN32 */
54 
55 /**
56  * For old systems that don't have lchown() use chown()
57  */
58 
59 #ifndef HAVE_LCHOWN
60 #define lchown chown
61 #endif
62 
63 /**
64  * For old systems that don't have lchmod() use chmod()
65  */
66 #ifndef HAVE_LCHMOD
67 #define lchmod chmod
68 #endif
69 
70 /*=============================================================*/
71 /*                                                             */
72 /*             ***  A l l  S y s t e m s ***                   */
73 /*                                                             */
74 /*=============================================================*/
75 
76 /**
77  * Return the data stream that will be used
78  */
SelectDataStream(FindFilesPacket * ff_pkt,bool compatible)79 int SelectDataStream(FindFilesPacket* ff_pkt, bool compatible)
80 {
81   int stream;
82 
83   /* This is a plugin special restore object */
84   if (ff_pkt->type == FT_RESTORE_FIRST) {
85     ClearAllBits(FO_MAX, ff_pkt->flags);
86     return STREAM_FILE_DATA;
87   }
88 
89   /*
90    * Fix all incompatible options
91    */
92 
93   /**
94    * No sparse option for encrypted data
95    */
96   if (BitIsSet(FO_ENCRYPT, ff_pkt->flags)) {
97     ClearBit(FO_SPARSE, ff_pkt->flags);
98   }
99 
100   /*
101    * Note, no sparse option for win32_data
102    */
103   if (!IsPortableBackup(&ff_pkt->bfd)) {
104     stream = STREAM_WIN32_DATA;
105     ClearBit(FO_SPARSE, ff_pkt->flags);
106   } else if (BitIsSet(FO_SPARSE, ff_pkt->flags)) {
107     stream = STREAM_SPARSE_DATA;
108   } else {
109     stream = STREAM_FILE_DATA;
110   }
111   if (BitIsSet(FO_OFFSETS, ff_pkt->flags)) { stream = STREAM_SPARSE_DATA; }
112 
113   /*
114    * Encryption is only supported for file data
115    */
116   if (stream != STREAM_FILE_DATA && stream != STREAM_WIN32_DATA &&
117       stream != STREAM_MACOS_FORK_DATA) {
118     ClearBit(FO_ENCRYPT, ff_pkt->flags);
119   }
120 
121   /*
122    * Compression is not supported for Mac fork data
123    */
124   if (stream == STREAM_MACOS_FORK_DATA) {
125     ClearBit(FO_COMPRESS, ff_pkt->flags);
126   }
127 
128   /*
129    * Handle compression and encryption options
130    */
131   if (BitIsSet(FO_COMPRESS, ff_pkt->flags)) {
132     if (compatible && ff_pkt->Compress_algo == COMPRESS_GZIP) {
133       switch (stream) {
134         case STREAM_WIN32_DATA:
135           stream = STREAM_WIN32_GZIP_DATA;
136           break;
137         case STREAM_SPARSE_DATA:
138           stream = STREAM_SPARSE_GZIP_DATA;
139           break;
140         case STREAM_FILE_DATA:
141           stream = STREAM_GZIP_DATA;
142           break;
143         default:
144           /**
145            * All stream types that do not support compression should clear out
146            * FO_COMPRESS above, and this code block should be unreachable.
147            */
148           ASSERT(!BitIsSet(FO_COMPRESS, ff_pkt->flags));
149           return STREAM_NONE;
150       }
151     } else {
152       switch (stream) {
153         case STREAM_WIN32_DATA:
154           stream = STREAM_WIN32_COMPRESSED_DATA;
155           break;
156         case STREAM_SPARSE_DATA:
157           stream = STREAM_SPARSE_COMPRESSED_DATA;
158           break;
159         case STREAM_FILE_DATA:
160           stream = STREAM_COMPRESSED_DATA;
161           break;
162         default:
163           /*
164            * All stream types that do not support compression should clear out
165            * FO_COMPRESS above, and this code block should be unreachable.
166            */
167           ASSERT(!BitIsSet(FO_COMPRESS, ff_pkt->flags));
168           return STREAM_NONE;
169       }
170     }
171   }
172 
173 #ifdef HAVE_CRYPTO
174   if (BitIsSet(FO_ENCRYPT, ff_pkt->flags)) {
175     switch (stream) {
176       case STREAM_WIN32_DATA:
177         stream = STREAM_ENCRYPTED_WIN32_DATA;
178         break;
179       case STREAM_WIN32_GZIP_DATA:
180         stream = STREAM_ENCRYPTED_WIN32_GZIP_DATA;
181         break;
182       case STREAM_WIN32_COMPRESSED_DATA:
183         stream = STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA;
184         break;
185       case STREAM_FILE_DATA:
186         stream = STREAM_ENCRYPTED_FILE_DATA;
187         break;
188       case STREAM_GZIP_DATA:
189         stream = STREAM_ENCRYPTED_FILE_GZIP_DATA;
190         break;
191       case STREAM_COMPRESSED_DATA:
192         stream = STREAM_ENCRYPTED_FILE_COMPRESSED_DATA;
193         break;
194       default:
195         /*
196          * All stream types that do not support encryption should clear out
197          * FO_ENCRYPT above, and this code block should be unreachable.
198          */
199         ASSERT(!BitIsSet(FO_ENCRYPT, ff_pkt->flags));
200         return STREAM_NONE;
201     }
202   }
203 #endif
204 
205   return stream;
206 }
207 
208 /**
209  * Restore all file attributes like owner, mode and file times.
210  */
RestoreFileAttributes(JobControlRecord * jcr,Attributes * attr,BareosWinFilePacket * ofd)211 static inline bool RestoreFileAttributes(JobControlRecord* jcr,
212                                          Attributes* attr,
213                                          BareosWinFilePacket* ofd)
214 {
215   bool ok = true;
216   bool suppress_errors;
217 #if defined(HAVE_FCHOWN) || defined(HAVE_FCHMOD) || defined(HAVE_FUTIMES) || \
218     defined(FUTIMENS)
219   bool file_is_open;
220 
221   /*
222    * Save if we are working on an open file.
223    */
224   file_is_open = IsBopen(ofd);
225 #endif
226 
227   /*
228    * See if we want to print errors.
229    */
230   suppress_errors = (debug_level >= 100 || my_uid != 0);
231 
232   /*
233    * Restore owner and group.
234    */
235 #ifdef HAVE_FCHOWN
236   if (file_is_open) {
237     if (fchown(ofd->fid, attr->statp.st_uid, attr->statp.st_gid) < 0 &&
238         !suppress_errors) {
239       BErrNo be;
240 
241       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file owner %s: ERR=%s\n"),
242             attr->ofname, be.bstrerror());
243       ok = false;
244     }
245   } else {
246 #else
247   {
248 #endif
249     if (lchown(attr->ofname, attr->statp.st_uid, attr->statp.st_gid) < 0 &&
250         !suppress_errors) {
251       BErrNo be;
252 
253       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file owner %s: ERR=%s\n"),
254             attr->ofname, be.bstrerror());
255       ok = false;
256     }
257   }
258 
259   /*
260    * Restore filemode.
261    */
262 #ifdef HAVE_FCHMOD
263   if (file_is_open) {
264     if (fchmod(ofd->fid, attr->statp.st_mode) < 0 && !suppress_errors) {
265       BErrNo be;
266 
267       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file modes %s: ERR=%s\n"),
268             attr->ofname, be.bstrerror());
269       ok = false;
270     }
271   } else {
272 #else
273   {
274 #endif
275 #if defined(HAVE_WIN32)
276     if (win32_chmod(attr->ofname, attr->statp.st_mode, attr->statp.st_rdev) <
277             0 &&
278         !suppress_errors) {
279 #else
280     if (lchmod(attr->ofname, attr->statp.st_mode) < 0 && !suppress_errors) {
281 #endif
282       BErrNo be;
283 
284       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file modes %s: ERR=%s\n"),
285             attr->ofname, be.bstrerror());
286       ok = false;
287     }
288   }
289 
290   /*
291    * Reset file times.
292    */
293 #if defined(HAVE_FUTIMES)
294   if (file_is_open) {
295     struct timeval restore_times[2];
296 
297     restore_times[0].tv_sec = attr->statp.st_atime;
298     restore_times[0].tv_usec = 0;
299     restore_times[1].tv_sec = attr->statp.st_mtime;
300     restore_times[1].tv_usec = 0;
301 
302     if (futimes(ofd->fid, restore_times) < 0 && !suppress_errors) {
303       BErrNo be;
304 
305       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
306             attr->ofname, be.bstrerror());
307       ok = false;
308     }
309   } else {
310 #elif defined(HAVE_FUTIMENS)
311   if (file_is_open) {
312     struct timespec restore_times[2];
313 
314     restore_times[0].tv_sec = attr->statp.st_atime;
315     restore_times[0].tv_nsec = 0;
316     restore_times[1].tv_sec = attr->statp.st_mtime;
317     restore_times[1].tv_nsec = 0;
318 
319     if (futimens(ofd->fid, restore_times) < 0 && !suppress_errors) {
320       BErrNo be;
321 
322       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
323             attr->ofname, be.bstrerror());
324       ok = false;
325     }
326   } else {
327 #else
328 {
329 #endif
330 #if defined(HAVE_LUTIMES)
331     struct timeval restore_times[2];
332 
333     restore_times[0].tv_sec = attr->statp.st_atime;
334     restore_times[0].tv_usec = 0;
335     restore_times[1].tv_sec = attr->statp.st_mtime;
336     restore_times[1].tv_usec = 0;
337 
338     if (lutimes(attr->ofname, restore_times) < 0 && !suppress_errors) {
339       BErrNo be;
340 
341       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
342             attr->ofname, be.bstrerror());
343       ok = false;
344     }
345 #elif defined(HAVE_UTIMES)
346     struct timeval restore_times[2];
347 
348     restore_times[0].tv_sec = attr->statp.st_atime;
349     restore_times[0].tv_usec = 0;
350     restore_times[1].tv_sec = attr->statp.st_mtime;
351     restore_times[1].tv_usec = 0;
352 
353     if (utimes(attr->ofname, restore_times) < 0 && !suppress_errors) {
354       BErrNo be;
355 
356       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
357             attr->ofname, be.bstrerror());
358       ok = false;
359     }
360 #else
361   struct utimbuf restore_times;
362 
363   restore_times.actime = attr->statp.st_atime;
364   restore_times.modtime = attr->statp.st_mtime;
365 
366   if (utime(attr->ofname, &restore_times) < 0 && !suppress_errors) {
367     BErrNo be;
368 
369     Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
370           attr->ofname, be.bstrerror());
371     ok = false;
372   }
373 #endif /* HAVE_LUTIMES */
374   }
375 
376   return ok;
377 }
378 
379 /**
380  * Set file modes, permissions and times
381  *
382  *  fname is the original filename
383  *  ofile is the output filename (may be in a different directory)
384  *
385  * Returns:  true  on success
386  *           false on failure
387  */
388 bool SetAttributes(JobControlRecord* jcr,
389                    Attributes* attr,
390                    BareosWinFilePacket* ofd)
391 {
392   mode_t old_mask;
393   bool ok = true;
394   bool suppress_errors;
395 
396   if (uid_set) {
397     my_uid = getuid();
398     my_gid = getgid();
399     uid_set = true;
400   }
401 
402   /*
403    * See if we want to print errors.
404    */
405   suppress_errors = (debug_level >= 100 || my_uid != 0);
406 
407 #if defined(HAVE_WIN32)
408   if (attr->stream == STREAM_UNIX_ATTRIBUTES_EX &&
409       set_win32_attributes(jcr, attr, ofd)) {
410     if (IsBopen(ofd)) { bclose(ofd); }
411     PmStrcpy(attr->ofname, "*None*");
412     return true;
413   }
414 
415   if (attr->data_stream == STREAM_WIN32_DATA ||
416       attr->data_stream == STREAM_WIN32_GZIP_DATA ||
417       attr->data_stream == STREAM_WIN32_COMPRESSED_DATA) {
418     if (IsBopen(ofd)) { bclose(ofd); }
419     PmStrcpy(attr->ofname, "*None*");
420     return true;
421   }
422 
423   /**
424    * If Windows stuff failed, e.g. attempt to restore Unix file to Windows,
425    * simply fall through and we will do it the universal way.
426    */
427 #endif
428 
429   old_mask = umask(0);
430   if (IsBopen(ofd)) {
431     boffset_t fsize;
432     char ec1[50], ec2[50];
433 
434     fsize = blseek(ofd, 0, SEEK_END);
435     if (attr->type == FT_REG && fsize > 0 && attr->statp.st_size > 0 &&
436         fsize != (boffset_t)attr->statp.st_size) {
437       Jmsg3(jcr, M_ERROR, 0,
438             _("File size of restored file %s not correct. Original %s, "
439               "restored %s.\n"),
440             attr->ofname, edit_uint64(attr->statp.st_size, ec1),
441             edit_uint64(fsize, ec2));
442     }
443   } else {
444     struct stat st;
445     char ec1[50], ec2[50];
446 
447     if (lstat(attr->ofname, &st) == 0) {
448       if (attr->type == FT_REG && st.st_size > 0 && attr->statp.st_size > 0 &&
449           st.st_size != attr->statp.st_size) {
450         Jmsg3(jcr, M_ERROR, 0,
451               _("File size of restored file %s not correct. Original %s, "
452                 "restored %s.\n"),
453               attr->ofname, edit_uint64(attr->statp.st_size, ec1),
454               edit_uint64(st.st_size, ec2));
455       }
456     }
457   }
458 
459   /**
460    * We do not restore sockets, so skip trying to restore their attributes.
461    */
462   if (attr->type == FT_SPEC && S_ISSOCK(attr->statp.st_mode)) { goto bail_out; }
463 
464   /* ***FIXME**** optimize -- don't do if already correct */
465   /**
466    * For link, change owner of link using lchown, but don't try to do a chmod as
467    * that will update the file behind it.
468    */
469   if (attr->type == FT_LNK) {
470     /*
471      * Change owner of link, not of real file
472      */
473     if (lchown(attr->ofname, attr->statp.st_uid, attr->statp.st_gid) < 0 &&
474         !suppress_errors) {
475       BErrNo be;
476 
477       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file owner %s: ERR=%s\n"),
478             attr->ofname, be.bstrerror());
479       ok = false;
480     }
481 
482 #ifdef HAVE_LCHMOD
483     if (lchmod(attr->ofname, attr->statp.st_mode) < 0 && !suppress_errors) {
484       BErrNo be;
485 
486       Jmsg2(jcr, M_ERROR, 0, _("Unable to set file modes %s: ERR=%s\n"),
487             attr->ofname, be.bstrerror());
488       ok = false;
489     }
490 #endif
491   } else {
492     if (!ofd->cmd_plugin) {
493       ok = RestoreFileAttributes(jcr, attr, ofd);
494 
495 #ifdef HAVE_CHFLAGS
496       /**
497        * FreeBSD user flags
498        *
499        * Note, this should really be done before the utime() above,
500        * but if the immutable bit is set, it will make the utimes()
501        * fail.
502        */
503       if (chflags(attr->ofname, attr->statp.st_flags) < 0 && !suppress_errors) {
504         BErrNo be;
505         Jmsg2(jcr, M_ERROR, 0, _("Unable to set file flags %s: ERR=%s\n"),
506               attr->ofname, be.bstrerror());
507         ok = false;
508       }
509 #endif
510     }
511   }
512 
513 bail_out:
514   if (IsBopen(ofd)) { bclose(ofd); }
515 
516   PmStrcpy(attr->ofname, "*None*");
517   umask(old_mask);
518 
519   return ok;
520 }
521 
522 #if !defined(HAVE_WIN32)
523 /*=============================================================*/
524 /*                                                             */
525 /*                 * * *  U n i x * * * *                      */
526 /*                                                             */
527 /*=============================================================*/
528 
529 /**
530  * It is possible to piggyback additional data e.g. ACLs on
531  *   the EncodeStat() data by returning the extended attributes
532  *   here.  They must be "self-contained" (i.e. you keep track
533  *   of your own length), and they must be in ASCII string
534  *   format. Using this feature is not recommended.
535  * The code below shows how to return nothing.  See the Win32
536  *   code below for returning something in the attributes.
537  */
538 int encode_attribsEx(JobControlRecord* jcr,
539                      char* attribsEx,
540                      FindFilesPacket* ff_pkt)
541 {
542 #ifdef HAVE_DARWIN_OS
543   /**
544    * We save the Mac resource fork length so that on a
545    * restore, we can be sure we put back the whole resource.
546    */
547   char* p;
548 
549   *attribsEx = 0; /* no extended attributes (yet) */
550   if (jcr->cmd_plugin || ff_pkt->type == FT_DELETED) {
551     return STREAM_UNIX_ATTRIBUTES;
552   }
553   p = attribsEx;
554   if (BitIsSet(FO_HFSPLUS, ff_pkt->flags)) {
555     p += ToBase64((uint64_t)(ff_pkt->hfsinfo.rsrclength), p);
556   }
557   *p = 0;
558 #else
559   *attribsEx = 0; /* no extended attributes */
560 #endif
561   return STREAM_UNIX_ATTRIBUTES;
562 }
563 #else
564 /*=============================================================*/
565 /*                                                             */
566 /*                 * * *  W i n 3 2 * * * *                    */
567 /*                                                             */
568 /*=============================================================*/
569 int encode_attribsEx(JobControlRecord* jcr,
570                      char* attribsEx,
571                      FindFilesPacket* ff_pkt)
572 {
573   char* p = attribsEx;
574   WIN32_FILE_ATTRIBUTE_DATA atts;
575   ULARGE_INTEGER li;
576 
577   attribsEx[0] = 0; /* no extended attributes */
578 
579   if (jcr->cmd_plugin || ff_pkt->type == FT_DELETED) {
580     return STREAM_UNIX_ATTRIBUTES;
581   }
582 
583   unix_name_to_win32(ff_pkt->sys_fname, ff_pkt->fname);
584   if (p_GetFileAttributesExW) {
585     /**
586      * Try unicode version
587      */
588     POOLMEM* pwszBuf = GetPoolMemory(PM_FNAME);
589     make_win32_path_UTF8_2_wchar(pwszBuf, ff_pkt->fname);
590 
591     BOOL b = p_GetFileAttributesExW((LPCWSTR)pwszBuf, GetFileExInfoStandard,
592                                     (LPVOID)&atts);
593     FreePoolMemory(pwszBuf);
594 
595     if (!b) {
596       WinError(jcr, "GetFileAttributesExW:", ff_pkt->sys_fname);
597       return STREAM_UNIX_ATTRIBUTES;
598     }
599   } else {
600     if (!p_GetFileAttributesExA) return STREAM_UNIX_ATTRIBUTES;
601 
602     if (!p_GetFileAttributesExA(ff_pkt->sys_fname, GetFileExInfoStandard,
603                                 (LPVOID)&atts)) {
604       WinError(jcr, "GetFileAttributesExA:", ff_pkt->sys_fname);
605       return STREAM_UNIX_ATTRIBUTES;
606     }
607   }
608 
609   /*
610    * Instead of using the current dwFileAttributes use the
611    * ff_pkt->statp.st_rdev which contains the actual fileattributes we
612    * want to save for this file.
613    */
614   atts.dwFileAttributes = ff_pkt->statp.st_rdev;
615 
616   p += ToBase64((uint64_t)atts.dwFileAttributes, p);
617   *p++ = ' '; /* separate fields with a space */
618   li.LowPart = atts.ftCreationTime.dwLowDateTime;
619   li.HighPart = atts.ftCreationTime.dwHighDateTime;
620   p += ToBase64((uint64_t)li.QuadPart, p);
621   *p++ = ' ';
622   li.LowPart = atts.ftLastAccessTime.dwLowDateTime;
623   li.HighPart = atts.ftLastAccessTime.dwHighDateTime;
624   p += ToBase64((uint64_t)li.QuadPart, p);
625   *p++ = ' ';
626   li.LowPart = atts.ftLastWriteTime.dwLowDateTime;
627   li.HighPart = atts.ftLastWriteTime.dwHighDateTime;
628   p += ToBase64((uint64_t)li.QuadPart, p);
629   *p++ = ' ';
630   p += ToBase64((uint64_t)atts.nFileSizeHigh, p);
631   *p++ = ' ';
632   p += ToBase64((uint64_t)atts.nFileSizeLow, p);
633   *p = 0;
634 
635   return STREAM_UNIX_ATTRIBUTES_EX;
636 }
637 
638 /**
639  * Do casting according to unknown type to keep compiler happy
640  */
641 #ifdef HAVE_TYPEOF
642 #define plug(st, val) st = (typeof st)val
643 #else
644 /*
645  * Use templates to do the casting
646  */
647 template <class T>
648 void plug(T& st, uint64_t val)
649 {
650   st = static_cast<T>(val);
651 }
652 #endif
653 
654 /**
655  * Set Extended File Attributes for Win32
656  *
657  * fname is the original filename
658  * ofile is the output filename (may be in a different directory)
659  *
660  * Returns:  true  on success
661  *           false on failure
662  */
663 static bool set_win32_attributes(JobControlRecord* jcr,
664                                  Attributes* attr,
665                                  BareosWinFilePacket* ofd)
666 {
667   char* p = attr->attrEx;
668   int64_t val;
669   WIN32_FILE_ATTRIBUTE_DATA atts;
670   ULARGE_INTEGER li;
671 
672   /** if we have neither Win ansi nor wchar API, get out */
673   if (!(p_SetFileAttributesW || p_SetFileAttributesA)) { return false; }
674 
675   if (!p || !*p) { /* we should have attributes */
676     Dmsg2(100, "Attributes missing. of=%s ofd=%d\n", attr->ofname, ofd->fid);
677     if (IsBopen(ofd)) { bclose(ofd); }
678     return false;
679   } else {
680     Dmsg2(100, "Attribs %s = %s\n", attr->ofname, attr->attrEx);
681   }
682 
683   p += FromBase64(&val, p);
684   plug(atts.dwFileAttributes, val);
685   p++; /* skip space */
686   p += FromBase64(&val, p);
687   li.QuadPart = val;
688   atts.ftCreationTime.dwLowDateTime = li.LowPart;
689   atts.ftCreationTime.dwHighDateTime = li.HighPart;
690   p++; /* skip space */
691   p += FromBase64(&val, p);
692   li.QuadPart = val;
693   atts.ftLastAccessTime.dwLowDateTime = li.LowPart;
694   atts.ftLastAccessTime.dwHighDateTime = li.HighPart;
695   p++; /* skip space */
696   p += FromBase64(&val, p);
697   li.QuadPart = val;
698   atts.ftLastWriteTime.dwLowDateTime = li.LowPart;
699   atts.ftLastWriteTime.dwHighDateTime = li.HighPart;
700   p++;
701   p += FromBase64(&val, p);
702   plug(atts.nFileSizeHigh, val);
703   p++;
704   p += FromBase64(&val, p);
705   plug(atts.nFileSizeLow, val);
706 
707   /** At this point, we have reconstructed the WIN32_FILE_ATTRIBUTE_DATA pkt */
708 
709   if (!IsBopen(ofd)) {
710     Dmsg1(100, "File not open: %s\n", attr->ofname);
711     bopen(ofd, attr->ofname, O_WRONLY | O_BINARY, 0,
712           0); /* attempt to open the file */
713   }
714 
715   /*
716    * Restore file attributes and times on the restored file.
717    */
718   if (!win32_restore_file_attributes(attr->ofname, BgetHandle(ofd), &atts)) {
719     WinError(jcr, "win32_restore_file_attributes:", attr->ofname);
720   }
721 
722   if (IsBopen(ofd)) { bclose(ofd); }
723 
724   return true;
725 }
726 
727 void WinError(JobControlRecord* jcr, const char* prefix, POOLMEM* win32_ofile)
728 {
729   DWORD lerror = GetLastError();
730   LPTSTR msg;
731   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
732                 NULL, lerror, 0, (LPTSTR)&msg, 0, NULL);
733   Dmsg3(100, "Error in %s on file %s: ERR=%s\n", prefix, win32_ofile, msg);
734   StripTrailingJunk(msg);
735   Jmsg3(jcr, M_ERROR, 0, _("Error in %s file %s: ERR=%s\n"), prefix,
736         win32_ofile, msg);
737   LocalFree(msg);
738 }
739 
740 void WinError(JobControlRecord* jcr, const char* prefix, DWORD lerror)
741 {
742   LPTSTR msg;
743   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
744                 NULL, lerror, 0, (LPTSTR)&msg, 0, NULL);
745   StripTrailingJunk(msg);
746   if (jcr) {
747     Jmsg2(jcr, M_ERROR, 0, _("Error in %s: ERR=%s\n"), prefix, msg);
748   } else {
749     MessageBox(NULL, msg, prefix, MB_OK);
750   }
751   LocalFree(msg);
752 }
753 #endif /* HAVE_WIN32 */
754