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