]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/spool.c
kes Add some additional locking in the cats directory in subroutines
[bacula/bacula] / bacula / src / stored / spool.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2004-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *  Spooling code
30  *
31  *      Kern Sibbald, March 2004
32  *
33  *  Version $Id$
34  */
35
36 #include "bacula.h"
37 #include "stored.h"
38
39 /* Forward referenced subroutines */
40 static void make_unique_data_spool_filename(DCR *dcr, POOLMEM **name);
41 static bool open_data_spool_file(DCR *dcr);
42 static bool close_data_spool_file(DCR *dcr);
43 static bool despool_data(DCR *dcr, bool commit);
44 static int  read_block_from_spool_file(DCR *dcr);
45 static bool open_attr_spool_file(JCR *jcr, BSOCK *bs);
46 static bool close_attr_spool_file(JCR *jcr, BSOCK *bs);
47 static bool write_spool_header(DCR *dcr);
48 static bool write_spool_data(DCR *dcr);
49
50 struct spool_stats_t {
51    uint32_t data_jobs;                /* current jobs spooling data */
52    uint32_t attr_jobs;
53    uint32_t total_data_jobs;          /* total jobs to have spooled data */
54    uint32_t total_attr_jobs;
55    int64_t max_data_size;             /* max data size */
56    int64_t max_attr_size;
57    int64_t data_size;                 /* current data size (all jobs running) */
58    int64_t attr_size;
59 };
60
61 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
62 spool_stats_t spool_stats;
63
64 /*
65  * Header for data spool record */
66 struct spool_hdr {
67    int32_t  FirstIndex;               /* FirstIndex for buffer */
68    int32_t  LastIndex;                /* LastIndex for buffer */
69    uint32_t len;                      /* length of next buffer */
70 };
71
72 enum {
73    RB_EOT = 1,
74    RB_ERROR,
75    RB_OK
76 };
77
78 void list_spool_stats(void sendit(const char *msg, int len, void *sarg), void *arg)
79 {
80    char ed1[30], ed2[30];
81    POOL_MEM msg(PM_MESSAGE);
82    int len;
83
84    len = Mmsg(msg, _("Spooling statistics:\n"));
85
86    if (spool_stats.data_jobs || spool_stats.max_data_size) {
87       len = Mmsg(msg, _("Data spooling: %u active jobs, %s bytes; %u total jobs, %s max bytes/job.\n"),
88          spool_stats.data_jobs, edit_uint64_with_commas(spool_stats.data_size, ed1),
89          spool_stats.total_data_jobs,
90          edit_uint64_with_commas(spool_stats.max_data_size, ed2));
91
92       sendit(msg.c_str(), len, arg);
93    }
94    if (spool_stats.attr_jobs || spool_stats.max_attr_size) {
95       len = Mmsg(msg, _("Attr spooling: %u active jobs, %s bytes; %u total jobs, %s max bytes.\n"),
96          spool_stats.attr_jobs, edit_uint64_with_commas(spool_stats.attr_size, ed1),
97          spool_stats.total_attr_jobs,
98          edit_uint64_with_commas(spool_stats.max_attr_size, ed2));
99    
100       sendit(msg.c_str(), len, arg);
101    }
102    len = Mmsg(msg, "====\n");
103    sendit(msg.c_str(), len, arg);
104 }
105
106 bool begin_data_spool(DCR *dcr)
107 {
108    bool stat = true;
109    if (!dcr->dev->is_dvd() && dcr->jcr->spool_data) {
110       Dmsg0(100, "Turning on data spooling\n");
111       dcr->spool_data = true;
112       stat = open_data_spool_file(dcr);
113       if (stat) {
114          dcr->spooling = true;
115          Jmsg(dcr->jcr, M_INFO, 0, _("Spooling data ...\n"));
116          P(mutex);
117          spool_stats.data_jobs++;
118          V(mutex);
119       }
120    }
121    return stat;
122 }
123
124 bool discard_data_spool(DCR *dcr)
125 {
126    if (dcr->spooling) {
127       Dmsg0(100, "Data spooling discarded\n");
128       return close_data_spool_file(dcr);
129    }
130    return true;
131 }
132
133 bool commit_data_spool(DCR *dcr)
134 {
135    bool stat;
136
137    if (dcr->spooling) {
138       Dmsg0(100, "Committing spooled data\n");
139       stat = despool_data(dcr, true /*commit*/);
140       if (!stat) {
141          Dmsg1(100, _("Bad return from despool WroteVol=%d\n"), dcr->WroteVol);
142          close_data_spool_file(dcr);
143          return false;
144       }
145       return close_data_spool_file(dcr);
146    }
147    return true;
148 }
149
150 static void make_unique_data_spool_filename(DCR *dcr, POOLMEM **name)
151 {
152    const char *dir;
153    if (dcr->dev->device->spool_directory) {
154       dir = dcr->dev->device->spool_directory;
155    } else {
156       dir = working_directory;
157    }
158    Mmsg(name, "%s/%s.data.%u.%s.%s.spool", dir, my_name, dcr->jcr->JobId,
159         dcr->jcr->Job, dcr->device->hdr.name);
160 }
161
162
163 static bool open_data_spool_file(DCR *dcr)
164 {
165    POOLMEM *name  = get_pool_memory(PM_MESSAGE);
166    int spool_fd;
167
168    make_unique_data_spool_filename(dcr, &name);
169    if ((spool_fd = open(name, O_CREAT|O_TRUNC|O_RDWR|O_BINARY, 0640)) >= 0) {
170       dcr->spool_fd = spool_fd;
171       dcr->jcr->spool_attributes = true;
172    } else {
173       berrno be;
174       Jmsg(dcr->jcr, M_FATAL, 0, _("Open data spool file %s failed: ERR=%s\n"), name,
175            be.bstrerror());
176       free_pool_memory(name);
177       return false;
178    }
179    Dmsg1(100, "Created spool file: %s\n", name);
180    free_pool_memory(name);
181    return true;
182 }
183
184 static bool close_data_spool_file(DCR *dcr)
185 {
186    POOLMEM *name  = get_pool_memory(PM_MESSAGE);
187
188    P(mutex);
189    spool_stats.data_jobs--;
190    spool_stats.total_data_jobs++;
191    if (spool_stats.data_size < dcr->job_spool_size) {
192       spool_stats.data_size = 0;
193    } else {
194       spool_stats.data_size -= dcr->job_spool_size;
195    }
196    dcr->job_spool_size = 0;
197    V(mutex);
198
199    make_unique_data_spool_filename(dcr, &name);
200    close(dcr->spool_fd);
201    dcr->spool_fd = -1;
202    dcr->spooling = false;
203    unlink(name);
204    Dmsg1(100, "Deleted spool file: %s\n", name);
205    free_pool_memory(name);
206    return true;
207 }
208
209 static const char *spool_name = "*spool*";
210
211 /*
212  * NB! This routine locks the device, but if committing will
213  *     not unlock it. If not committing, it will be unlocked.
214  */
215 static bool despool_data(DCR *dcr, bool commit)
216 {
217    DEVICE *rdev;
218    DCR *rdcr;
219    bool ok = true;
220    DEV_BLOCK *block;
221    JCR *jcr = dcr->jcr;
222    int stat;
223    char ec1[50];
224
225    Dmsg0(100, "Despooling data\n");
226    /*
227     * Commit means that the job is done, so we commit, otherwise, we
228     *  are despooling because of user spool size max or some error  
229     *  (e.g. filesystem full).
230     */
231    if (commit) {
232       Jmsg(jcr, M_INFO, 0, _("Committing spooled data to Volume \"%s\". Despooling %s bytes ...\n"),
233          jcr->dcr->VolumeName,
234          edit_uint64_with_commas(jcr->dcr->job_spool_size, ec1));
235    } else {
236       Jmsg(jcr, M_INFO, 0, _("Writing spooled data to Volume. Despooling %s bytes ...\n"),
237          edit_uint64_with_commas(jcr->dcr->job_spool_size, ec1));
238    }
239    dcr->despool_wait = true;
240    dcr->spooling = false;
241    dcr->dev->r_dlock();
242    dcr->despool_wait = false;
243    dcr->despooling = true;
244    dcr->dev_locked = true;
245
246    /*
247     * This is really quite kludgy and should be fixed some time.
248     * We create a dev structure to read from the spool file
249     * in rdev and rdcr.
250     */
251    rdev = (DEVICE *)malloc(sizeof(DEVICE));
252    memset(rdev, 0, sizeof(DEVICE));
253    rdev->dev_name = get_memory(strlen(spool_name)+1);
254    bstrncpy(rdev->dev_name, spool_name, sizeof(rdev->dev_name));
255    rdev->errmsg = get_pool_memory(PM_EMSG);
256    *rdev->errmsg = 0;
257    rdev->max_block_size = dcr->dev->max_block_size;
258    rdev->min_block_size = dcr->dev->min_block_size;
259    rdev->device = dcr->dev->device;
260    rdcr = new_dcr(jcr, NULL, rdev);
261    rdcr->spool_fd = dcr->spool_fd;
262    block = dcr->block;                /* save block */
263    dcr->block = rdcr->block;          /* make read and write block the same */
264
265    Dmsg1(800, "read/write block size = %d\n", block->buf_len);
266    lseek(rdcr->spool_fd, 0, SEEK_SET); /* rewind */
267
268 #if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
269    posix_fadvise(rdcr->spool_fd, 0, 0, POSIX_FADV_WILLNEED);
270 #endif
271
272    /* Add run time, to get current wait time */
273    time_t despool_start = time(NULL) - jcr->run_time;
274
275    for ( ; ok; ) {
276       if (job_canceled(jcr)) {
277          ok = false;
278          break;
279       }
280       stat = read_block_from_spool_file(rdcr);
281       if (stat == RB_EOT) {
282          break;
283       } else if (stat == RB_ERROR) {
284          ok = false;
285          break;
286       }
287       ok = write_block_to_device(dcr);
288       if (!ok) {
289          Jmsg2(jcr, M_FATAL, 0, _("Fatal append error on device %s: ERR=%s\n"),
290                dcr->dev->print_name(), dcr->dev->bstrerror());
291       }
292       Dmsg3(800, "Write block ok=%d FI=%d LI=%d\n", ok, block->FirstIndex, block->LastIndex);
293    }
294
295    /* Subtracting run_time give us elapsed time - wait_time since we started despooling */
296    time_t despool_elapsed = time(NULL) - despool_start - jcr->run_time;
297
298    if (despool_elapsed <= 0) {
299       despool_elapsed = 1;
300    }
301
302    Jmsg(dcr->jcr, M_INFO, 0, _("Despooling elapsed time = %02d:%02d:%02d, Transfer rate = %s bytes/second\n"),
303          despool_elapsed / 3600, despool_elapsed % 3600 / 60, despool_elapsed % 60,
304          edit_uint64_with_suffix(jcr->dcr->job_spool_size / despool_elapsed, ec1));
305
306    dcr->block = block;                /* reset block */
307
308    lseek(rdcr->spool_fd, 0, SEEK_SET); /* rewind */
309    if (ftruncate(rdcr->spool_fd, 0) != 0) {
310       berrno be;
311       Jmsg(dcr->jcr, M_ERROR, 0, _("Ftruncate spool file failed: ERR=%s\n"),
312          be.bstrerror());
313       /* Note, try continuing despite ftruncate problem */
314    }
315
316    P(mutex);
317    if (spool_stats.data_size < dcr->job_spool_size) {
318       spool_stats.data_size = 0;
319    } else {
320       spool_stats.data_size -= dcr->job_spool_size;
321    }
322    V(mutex);
323    P(dcr->dev->spool_mutex);
324    dcr->dev->spool_size -= dcr->job_spool_size;
325    dcr->job_spool_size = 0;            /* zap size in input dcr */
326    V(dcr->dev->spool_mutex);
327    free_memory(rdev->dev_name);
328    free_pool_memory(rdev->errmsg);
329    /* Be careful to NULL the jcr and free rdev after free_dcr() */
330    rdcr->jcr = NULL;
331    rdcr->dev = NULL;
332    free_dcr(rdcr);
333    free(rdev);
334    dcr->spooling = true;           /* turn on spooling again */
335    dcr->despooling = false;
336    /* If doing a commit, leave the device locked -- unlocked in release_device() */
337    if (!commit) {
338       dcr->dev_locked = false;
339       dcr->dev->dunlock();
340    }
341    return ok;
342 }
343
344 /*
345  * Read a block from the spool file
346  *
347  *  Returns RB_OK on success
348  *          RB_EOT when file done
349  *          RB_ERROR on error
350  */
351 static int read_block_from_spool_file(DCR *dcr)
352 {
353    uint32_t rlen;
354    ssize_t stat;
355    spool_hdr hdr;
356    DEV_BLOCK *block = dcr->block;
357
358    rlen = sizeof(hdr);
359    stat = read(dcr->spool_fd, (char *)&hdr, (size_t)rlen);
360    if (stat == 0) {
361       Dmsg0(100, "EOT on spool read.\n");
362       return RB_EOT;
363    } else if (stat != (ssize_t)rlen) {
364       if (stat == -1) {
365          berrno be;
366          Jmsg(dcr->jcr, M_FATAL, 0, _("Spool header read error. ERR=%s\n"),
367               be.bstrerror());
368       } else {
369          Pmsg2(000, _("Spool read error. Wanted %u bytes, got %d\n"), rlen, stat);
370          Jmsg2(dcr->jcr, M_FATAL, 0, _("Spool header read error. Wanted %u bytes, got %d\n"), rlen, stat);
371       }
372       return RB_ERROR;
373    }
374    rlen = hdr.len;
375    if (rlen > block->buf_len) {
376       Pmsg2(000, _("Spool block too big. Max %u bytes, got %u\n"), block->buf_len, rlen);
377       Jmsg2(dcr->jcr, M_FATAL, 0, _("Spool block too big. Max %u bytes, got %u\n"), block->buf_len, rlen);
378       return RB_ERROR;
379    }
380    stat = read(dcr->spool_fd, (char *)block->buf, (size_t)rlen);
381    if (stat != (ssize_t)rlen) {
382       Pmsg2(000, _("Spool data read error. Wanted %u bytes, got %d\n"), rlen, stat);
383       Jmsg2(dcr->jcr, M_FATAL, 0, _("Spool data read error. Wanted %u bytes, got %d\n"), rlen, stat);
384       return RB_ERROR;
385    }
386    /* Setup write pointers */
387    block->binbuf = rlen;
388    block->bufp = block->buf + block->binbuf;
389    block->FirstIndex = hdr.FirstIndex;
390    block->LastIndex = hdr.LastIndex;
391    block->VolSessionId = dcr->jcr->VolSessionId;
392    block->VolSessionTime = dcr->jcr->VolSessionTime;
393    Dmsg2(800, "Read block FI=%d LI=%d\n", block->FirstIndex, block->LastIndex);
394    return RB_OK;
395 }
396
397 /*
398  * Write a block to the spool file
399  *
400  *  Returns: true on success or EOT
401  *           false on hard error
402  */
403 bool write_block_to_spool_file(DCR *dcr)
404 {
405    uint32_t wlen, hlen;               /* length to write */
406    bool despool = false;
407    DEV_BLOCK *block = dcr->block;
408
409    ASSERT(block->binbuf == ((uint32_t) (block->bufp - block->buf)));
410    if (block->binbuf <= WRITE_BLKHDR_LENGTH) {  /* Does block have data in it? */
411       return true;
412    }
413
414    hlen = sizeof(spool_hdr);
415    wlen = block->binbuf;
416    P(dcr->dev->spool_mutex);
417    dcr->job_spool_size += hlen + wlen;
418    dcr->dev->spool_size += hlen + wlen;
419    if ((dcr->max_job_spool_size > 0 && dcr->job_spool_size >= dcr->max_job_spool_size) ||
420        (dcr->dev->max_spool_size > 0 && dcr->dev->spool_size >= dcr->dev->max_spool_size)) {
421       despool = true;
422    }
423    V(dcr->dev->spool_mutex);
424    P(mutex);
425    spool_stats.data_size += hlen + wlen;
426    if (spool_stats.data_size > spool_stats.max_data_size) {
427       spool_stats.max_data_size = spool_stats.data_size;
428    }
429    V(mutex);
430    if (despool) {
431 #ifdef xDEBUG
432       char ec1[30], ec2[30], ec3[30], ec4[30];
433       Dmsg4(100, "Despool in write_block_to_spool_file max_size=%s size=%s "
434             "max_job_size=%s job_size=%s\n",
435             edit_uint64_with_commas(dcr->max_job_spool_size, ec1),
436             edit_uint64_with_commas(dcr->job_spool_size, ec2),
437             edit_uint64_with_commas(dcr->dev->max_spool_size, ec3),
438             edit_uint64_with_commas(dcr->dev->spool_size, ec4));
439 #endif
440       Jmsg(dcr->jcr, M_INFO, 0, _("User specified spool size reached.\n"));
441       if (!despool_data(dcr, false)) {
442          Pmsg0(000, _("Bad return from despool in write_block.\n"));
443          return false;
444       }
445       /* Despooling cleared these variables so reset them */
446       P(dcr->dev->spool_mutex);
447       dcr->job_spool_size += hlen + wlen;
448       dcr->dev->spool_size += hlen + wlen;
449       V(dcr->dev->spool_mutex);
450       Jmsg(dcr->jcr, M_INFO, 0, _("Spooling data again ...\n"));
451    }
452
453
454    if (!write_spool_header(dcr)) {
455       return false;
456    }
457    if (!write_spool_data(dcr)) {
458      return false;
459    }
460
461    Dmsg2(800, "Wrote block FI=%d LI=%d\n", block->FirstIndex, block->LastIndex);
462    empty_block(block);
463    return true;
464 }
465
466 static bool write_spool_header(DCR *dcr)
467 {
468    spool_hdr hdr;
469    ssize_t stat;
470    DEV_BLOCK *block = dcr->block;
471
472    hdr.FirstIndex = block->FirstIndex;
473    hdr.LastIndex = block->LastIndex;
474    hdr.len = block->binbuf;
475
476    /* Write header */
477    for (int retry=0; retry<=1; retry++) {
478       stat = write(dcr->spool_fd, (char*)&hdr, sizeof(hdr));
479       if (stat == -1) {
480          berrno be;
481          Jmsg(dcr->jcr, M_FATAL, 0, _("Error writing header to spool file. ERR=%s\n"),
482               be.bstrerror());
483       }
484       if (stat != (ssize_t)sizeof(hdr)) {
485          /* If we wrote something, truncate it, then despool */
486          if (stat != -1) {
487 #if defined(HAVE_WIN32)
488             boffset_t   pos = _lseeki64(dcr->spool_fd, (__int64)0, SEEK_CUR);
489 #else
490             boffset_t   pos = lseek(dcr->spool_fd, (off_t)0, SEEK_CUR);
491 #endif
492             if (ftruncate(dcr->spool_fd, pos - stat) != 0) {
493                berrno be;
494                Jmsg(dcr->jcr, M_ERROR, 0, _("Ftruncate spool file failed: ERR=%s\n"),
495                   be.bstrerror());
496               /* Note, try continuing despite ftruncate problem */
497             }
498          }
499          if (!despool_data(dcr, false)) {
500             Jmsg(dcr->jcr, M_FATAL, 0, _("Fatal despooling error."));
501             return false;
502          }
503          continue;                    /* try again */
504       }
505       return true;
506    }
507    Jmsg(dcr->jcr, M_FATAL, 0, _("Retrying after header spooling error failed.\n"));
508    return false;
509 }
510
511 static bool write_spool_data(DCR *dcr)
512 {
513    ssize_t stat;
514    DEV_BLOCK *block = dcr->block;
515
516    /* Write data */
517    for (int retry=0; retry<=1; retry++) {
518       stat = write(dcr->spool_fd, block->buf, (size_t)block->binbuf);
519       if (stat == -1) {
520          berrno be;
521          Jmsg(dcr->jcr, M_FATAL, 0, _("Error writing data to spool file. ERR=%s\n"),
522               be.bstrerror());
523       }
524       if (stat != (ssize_t)block->binbuf) {
525          /*
526           * If we wrote something, truncate it and the header, then despool
527           */
528          if (stat != -1) {
529 #if defined(HAVE_WIN32)
530             boffset_t   pos = _lseeki64(dcr->spool_fd, (__int64)0, SEEK_CUR);
531 #else
532             boffset_t   pos = lseek(dcr->spool_fd, (off_t)0, SEEK_CUR);
533 #endif
534             if (ftruncate(dcr->spool_fd, pos - stat - sizeof(spool_hdr)) != 0) {
535                berrno be;
536                Jmsg(dcr->jcr, M_ERROR, 0, _("Ftruncate spool file failed: ERR=%s\n"),
537                   be.bstrerror());
538                /* Note, try continuing despite ftruncate problem */
539             }
540          }
541          if (!despool_data(dcr, false)) {
542             Jmsg(dcr->jcr, M_FATAL, 0, _("Fatal despooling error."));
543             return false;
544          }
545          if (!write_spool_header(dcr)) {
546             return false;
547          }
548          continue;                    /* try again */
549       }
550       return true;
551    }
552    Jmsg(dcr->jcr, M_FATAL, 0, _("Retrying after data spooling error failed.\n"));
553    return false;
554 }
555
556
557
558 bool are_attributes_spooled(JCR *jcr)
559 {
560    return jcr->spool_attributes && jcr->dir_bsock->m_spool_fd;
561 }
562
563 /*
564  * Create spool file for attributes.
565  *  This is done by "attaching" to the bsock, and when
566  *  it is called, the output is written to a file.
567  *  The actual spooling is turned on and off in
568  *  append.c only during writing of the attributes.
569  */
570 bool begin_attribute_spool(JCR *jcr)
571 {
572    if (!jcr->no_attributes && jcr->spool_attributes) {
573       return open_attr_spool_file(jcr, jcr->dir_bsock);
574    }
575    return true;
576 }
577
578 bool discard_attribute_spool(JCR *jcr)
579 {
580    if (are_attributes_spooled(jcr)) {
581       return close_attr_spool_file(jcr, jcr->dir_bsock);
582    }
583    return true;
584 }
585
586 static void update_attr_spool_size(ssize_t size)
587 {
588    P(mutex);
589    if (size > 0) {
590      if ((spool_stats.attr_size - size) > 0) {
591         spool_stats.attr_size -= size;
592      } else {
593         spool_stats.attr_size = 0;
594      }
595    }
596    V(mutex);
597 }
598
599 bool commit_attribute_spool(JCR *jcr)
600 {
601    off_t size;
602    char ec1[30];
603
604    if (are_attributes_spooled(jcr)) {
605       if (fseeko(jcr->dir_bsock->m_spool_fd, 0, SEEK_END) != 0) {
606          berrno be;
607          Jmsg(jcr, M_FATAL, 0, _("Fseek on attributes file failed: ERR=%s\n"),
608               be.bstrerror());
609          goto bail_out;
610       }
611       size = ftello(jcr->dir_bsock->m_spool_fd);
612       if (size < 0) {
613          berrno be;
614          Jmsg(jcr, M_FATAL, 0, _("Fseek on attributes file failed: ERR=%s\n"),
615               be.bstrerror());
616          goto bail_out;
617       }
618       P(mutex);
619       if (spool_stats.attr_size + size > spool_stats.max_attr_size) {
620          spool_stats.max_attr_size = spool_stats.attr_size + size;
621       }
622       spool_stats.attr_size += size;
623       V(mutex);
624       Jmsg(jcr, M_INFO, 0, _("Sending spooled attrs to the Director. Despooling %s bytes ...\n"),
625             edit_uint64_with_commas(size, ec1));
626       jcr->dir_bsock->despool(update_attr_spool_size, size);
627       return close_attr_spool_file(jcr, jcr->dir_bsock);
628    }
629    return true;
630
631 bail_out:
632    close_attr_spool_file(jcr, jcr->dir_bsock);
633    return false;
634 }
635
636 static void make_unique_spool_filename(JCR *jcr, POOLMEM **name, int fd)
637 {
638    Mmsg(name, "%s/%s.attr.%s.%d.spool", working_directory, my_name,
639       jcr->Job, fd);
640 }
641
642
643 bool open_attr_spool_file(JCR *jcr, BSOCK *bs)
644 {
645    POOLMEM *name  = get_pool_memory(PM_MESSAGE);
646
647    make_unique_spool_filename(jcr, &name, bs->m_fd);
648    bs->m_spool_fd = fopen(name, "w+b");
649    if (!bs->m_spool_fd) {
650       berrno be;
651       Jmsg(jcr, M_FATAL, 0, _("fopen attr spool file %s failed: ERR=%s\n"), name,
652            be.bstrerror());
653       free_pool_memory(name);
654       return false;
655    }
656    P(mutex);
657    spool_stats.attr_jobs++;
658    V(mutex);
659    free_pool_memory(name);
660    return true;
661 }
662
663 bool close_attr_spool_file(JCR *jcr, BSOCK *bs)
664 {
665    POOLMEM *name;
666
667    if (!bs->m_spool_fd) {
668       return true;
669    }
670    name = get_pool_memory(PM_MESSAGE);
671    P(mutex);
672    spool_stats.attr_jobs--;
673    spool_stats.total_attr_jobs++;
674    V(mutex);
675    make_unique_spool_filename(jcr, &name, bs->m_fd);
676    fclose(bs->m_spool_fd);
677    unlink(name);
678    free_pool_memory(name);
679    bs->m_spool_fd = NULL;
680    bs->m_spool = false;
681    return true;
682 }