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