]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/acquire.c
kes Fix logic error in handling error return from mtx-changer
[bacula/bacula] / bacula / src / stored / acquire.c
1 /*
2  *  Routines to acquire and release a device for read/write
3  *
4  *   Kern Sibbald, August MMII
5  *
6  *   Version $Id$
7  */
8 /*
9    Copyright (C) 2002-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"                   /* pull in global headers */
24 #include "stored.h"                   /* pull in Storage Deamon headers */
25
26 /* Forward referenced functions */
27 static void detach_dcr_from_dev(DCR *dcr);
28
29
30 /*********************************************************************
31  * Acquire device for reading. 
32  *  The drive should have previously been reserved by calling 
33  *  reserve_device_for_read(). We read the Volume label from the block and
34  *  leave the block pointers just after the label.
35  *
36  *  Returns: NULL if failed for any reason
37  *           dcr  if successful
38  */
39 bool acquire_device_for_read(DCR *dcr)
40 {
41    DEVICE *dev = dcr->dev;
42    JCR *jcr = dcr->jcr;
43    bool ok = false;
44    bool tape_previously_mounted;
45    bool tape_initially_mounted;
46    VOL_LIST *vol;
47    bool try_autochanger = true;
48    int i;
49    int vol_label_status;
50    int retry = 0;
51    
52    Dmsg1(50, "jcr->dcr=%p\n", jcr->dcr);
53    dev->block(BST_DOING_ACQUIRE);
54
55    if (dev->num_writers > 0) {
56       Jmsg2(jcr, M_FATAL, 0, _("Num_writers=%d not zero. Job %d canceled.\n"), 
57          dev->num_writers, jcr->JobId);
58       goto get_out;
59    }
60
61    /* Find next Volume, if any */
62    vol = jcr->VolList;
63    if (!vol) {
64       Jmsg(jcr, M_FATAL, 0, _("No volumes specified. Job %d canceled.\n"), jcr->JobId);
65       goto get_out;
66    }
67    jcr->CurVolume++;
68    for (i=1; i<jcr->CurVolume; i++) {
69       vol = vol->next;
70    }
71    if (!vol) {
72       goto get_out;                   /* should not happen */   
73    }
74    bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName));
75    bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type));
76    dcr->VolCatInfo.Slot = vol->Slot;
77     
78    /*
79     * If the MediaType requested for this volume is not the
80     *  same as the current drive, we attempt to find the same
81     *  device that was used to write the orginal volume.  If
82     *  found, we switch to using that device.
83     */
84    Dmsg2(100, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type);
85    if (dcr->media_type[0] && strcmp(dcr->media_type, dev->device->media_type) != 0) {
86       RCTX rctx;
87       DIRSTORE *store;
88       int stat;
89       DCR *dcr_save = jcr->dcr;
90
91       lock_reservations();
92       jcr->dcr = NULL;
93       memset(&rctx, 0, sizeof(RCTX));
94       rctx.jcr = jcr;
95       jcr->reserve_msgs = New(alist(10, not_owned_by_alist));
96       rctx.any_drive = true;
97       rctx.device_name = vol->device;
98       store = new DIRSTORE;
99       memset(store, 0, sizeof(DIRSTORE));
100       store->name[0] = 0; /* No dir name */
101       bstrncpy(store->media_type, vol->MediaType, sizeof(store->media_type));
102       bstrncpy(store->pool_name, dcr->pool_name, sizeof(store->pool_name));
103       bstrncpy(store->pool_type, dcr->pool_type, sizeof(store->pool_type));
104       store->append = false;
105       rctx.store = store;
106       
107       /*
108        * Note, if search_for_device() succeeds, we get a new_dcr,
109        *  which we do not use except for the dev info.
110        */
111       stat = search_res_for_device(rctx);
112       release_msgs(jcr);              /* release queued messages */
113       unlock_reservations();
114       if (stat == 1) {
115          DCR *new_dcr = jcr->read_dcr;
116          dev->unblock();
117          detach_dcr_from_dev(dcr);    /* release old device */
118          /* Copy important info from the new dcr */
119          dev = dcr->dev = new_dcr->dev; 
120          jcr->read_dcr = dcr; 
121          dcr->device = new_dcr->device;
122          dcr->max_job_spool_size = dcr->device->max_job_spool_size;
123          if (dev->fd != 0 && jcr && jcr->JobType != JT_SYSTEM) {
124             dev->attached_dcrs->append(dcr);  /* attach dcr to device */
125          }
126          new_dcr->VolumeName[0] = 0;
127          free_dcr(new_dcr);
128          dev->block(BST_DOING_ACQUIRE); 
129          Jmsg(jcr, M_INFO, 0, _("Media Type change.  New device %s chosen.\n"),
130             dev->print_name());
131          bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName));
132          bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type));
133          dcr->VolCatInfo.Slot = vol->Slot;
134          bstrncpy(dcr->pool_name, store->pool_name, sizeof(dcr->pool_name));
135          bstrncpy(dcr->pool_type, store->pool_type, sizeof(dcr->pool_type));
136       } else if (stat == 0) {   /* device busy */
137          Dmsg1(000, "Device %s is busy.\n", vol->device);
138       } else {
139          /* error */
140          Jmsg1(jcr, M_FATAL, 0, _("No suitable device found to read Volume \"%s\"\n"),
141             vol->VolumeName);
142          jcr->dcr = dcr_save;
143          goto get_out;
144       }
145       jcr->dcr = dcr_save;
146    }
147
148
149    init_device_wait_timers(dcr);
150
151    tape_previously_mounted = dev->can_read() || dev->can_append() ||
152                              dev->is_labeled();
153    tape_initially_mounted = tape_previously_mounted;
154
155
156    /* Volume info is always needed because of VolParts */
157    Dmsg0(200, "dir_get_volume_info\n");
158    if (!dir_get_volume_info(dcr, GET_VOL_INFO_FOR_READ)) {
159       Jmsg1(jcr, M_WARNING, 0, "%s", jcr->errmsg);
160    }
161    
162    for ( ;; ) {
163       /* If not polling limit retries */
164       if (!dev->poll && retry++ > 10) {
165          break;
166       }
167       dev->clear_labeled();              /* force reread of label */
168       if (job_canceled(jcr)) {
169          Mmsg1(dev->errmsg, _("Job %d canceled.\n"), jcr->JobId);
170          goto get_out;                /* error return */
171       }
172       /*
173        * This code ensures that the device is ready for
174        * reading. If it is a file, it opens it.
175        * If it is a tape, it checks the volume name
176        */
177       Dmsg1(100, "bstored: open vol=%s\n", dcr->VolumeName);
178       if (dev->open(dcr, OPEN_READ_ONLY) < 0) {
179          if (dev->dev_errno == EIO) {   /* no tape loaded */
180            Jmsg3(jcr, M_WARNING, 0, _("Read open device %s Volume \"%s\" failed (EIO): ERR=%s\n"),
181                  dev->print_name(), dcr->VolumeName, dev->bstrerror());
182             goto default_path;
183          }
184          
185          Jmsg3(jcr, M_FATAL, 0, _("Read open device %s Volume \"%s\" failed: ERR=%s\n"),
186              dev->print_name(), dcr->VolumeName, dev->bstrerror());
187          goto get_out;
188       }
189       Dmsg1(100, "opened dev %s OK\n", dev->print_name());
190       
191       /* Read Volume Label */
192       
193       Dmsg0(200, "calling read-vol-label\n");
194       vol_label_status = read_dev_volume_label(dcr);
195       switch (vol_label_status) {
196       case VOL_OK:
197          ok = true;
198          memcpy(&dev->VolCatInfo, &dcr->VolCatInfo, sizeof(dev->VolCatInfo));
199          break;                    /* got it */
200       case VOL_IO_ERROR:
201          /*
202           * Send error message generated by read_dev_volume_label()
203           *  only we really had a tape mounted. This supresses superfluous
204           *  error messages when nothing is mounted.
205           */
206          if (tape_previously_mounted) {
207             Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
208          }
209          goto default_path;
210       case VOL_NAME_ERROR:
211          if (tape_initially_mounted) {
212             tape_initially_mounted = false;
213             goto default_path;
214          }
215          /* If polling and got a previous bad name, ignore it */
216          if (dev->poll && strcmp(dev->BadVolName, dev->VolHdr.VolumeName) == 0) {
217             goto default_path;
218          } else {
219              bstrncpy(dev->BadVolName, dev->VolHdr.VolumeName, sizeof(dev->BadVolName));
220          }
221          /* Fall through */
222       default:
223          Jmsg1(jcr, M_WARNING, 0, "%s", jcr->errmsg);
224 default_path:
225          tape_previously_mounted = true;
226          
227          /*
228           * If the device requires mount, close it, so the device can be ejected.
229           */
230          if (dev->requires_mount()) {
231             dev->close();
232          }
233          
234          /* Call autochanger only once unless ask_sysop called */
235          if (try_autochanger) {
236             int stat;
237             Dmsg2(200, "calling autoload Vol=%s Slot=%d\n",
238                dcr->VolumeName, dcr->VolCatInfo.Slot);
239             stat = autoload_device(dcr, 0, NULL);
240             if (stat > 0) {
241                try_autochanger = false;
242                continue;              /* try reading volume mounted */
243             }
244          }
245          
246          /* Mount a specific volume and no other */
247          Dmsg0(200, "calling dir_ask_sysop\n");
248          if (!dir_ask_sysop_to_mount_volume(dcr)) {
249             goto get_out;             /* error return */
250          }
251          try_autochanger = true;      /* permit using autochanger again */
252          continue;                    /* try reading again */
253       } /* end switch */
254       break;
255    } /* end for loop */
256    if (!ok) {
257       Jmsg1(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s.\n"),
258             dev->print_name());
259       goto get_out;
260    }
261
262    dev->clear_append();
263    dev->set_read();
264    set_jcr_job_status(jcr, JS_Running);
265    dir_send_job_status(jcr);
266    Jmsg(jcr, M_INFO, 0, _("Ready to read from volume \"%s\" on device %s.\n"),
267       dcr->VolumeName, dev->print_name());
268
269 get_out:
270    P(dev->mutex);
271    if (dcr->reserved_device) {
272       dev->reserved_device--;
273       Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
274       dcr->reserved_device = false;
275    }
276    V(dev->mutex);
277    dev->unblock();
278    Dmsg1(50, "jcr->dcr=%p\n", jcr->dcr);
279    return ok;
280 }
281
282
283 /*
284  * Acquire device for writing. We permit multiple writers.
285  *  If this is the first one, we read the label.
286  *
287  *  Returns: NULL if failed for any reason
288  *           dcr if successful.
289  *   Note, normally reserve_device_for_append() is called
290  *   before this routine.
291  */
292 DCR *acquire_device_for_append(DCR *dcr)
293 {
294    bool release = false;
295    bool recycle = false;
296    bool do_mount = false;
297    DEVICE *dev = dcr->dev;
298    JCR *jcr = dcr->jcr;
299
300    init_device_wait_timers(dcr);
301
302    dev->block(BST_DOING_ACQUIRE);
303    Dmsg1(190, "acquire_append device is %s\n", dev->is_tape()?"tape":
304         (dev->is_dvd()?"DVD":"disk"));
305
306    /*
307     * With the reservation system, this should not happen
308     */
309    if (dev->can_read()) {
310       Jmsg1(jcr, M_FATAL, 0, _("Device %s is busy reading.\n"), dev->print_name());
311       Dmsg1(200, "Device %s is busy reading.\n", dev->print_name());
312       goto get_out;
313    }
314
315    if (dev->can_append()) {
316       Dmsg0(190, "device already in append.\n");
317       /*
318        * Device already in append mode
319        *
320        * Check if we have the right Volume mounted
321        *   OK if current volume info OK
322        *   OK if next volume matches current volume
323        *   otherwise mount desired volume obtained from
324        *    dir_find_next_appendable_volume
325        *  dev->VolHdr.VolumeName is what is in the drive
326        *  dcr->VolumeName is what we pass into the routines, or
327        *    get back from the subroutines.
328        */
329       bstrncpy(dcr->VolumeName, dev->VolHdr.VolumeName, sizeof(dcr->VolumeName));
330       if (!dir_get_volume_info(dcr, GET_VOL_INFO_FOR_WRITE) &&
331           !(dir_find_next_appendable_volume(dcr) &&
332             strcmp(dev->VolHdr.VolumeName, dcr->VolumeName) == 0)) { /* wrong tape mounted */
333          Dmsg2(190, "Wrong tape mounted: %s. wants:%s\n", dev->VolHdr.VolumeName,
334             dcr->VolumeName);
335          /* Release volume reserved by dir_find_next_appendable_volume() */
336          if (dcr->VolumeName[0]) {
337             free_unused_volume(dcr);
338          }
339          if (dev->num_writers != 0) {
340             Jmsg3(jcr, M_FATAL, 0, _("Wanted Volume \"%s\", but device %s is busy writing on \"%s\" .\n"), 
341                  dcr->VolumeName, dev->print_name(), dev->VolHdr.VolumeName);
342             Dmsg3(200, "Wanted Volume \"%s\", but device %s is busy writing on \"%s\" .\n",  
343                  dcr->VolumeName, dev->print_name(), dev->VolHdr.VolumeName);
344             goto get_out;
345          }
346          /* Wrong tape mounted, release it, then fall through to get correct one */
347          Dmsg0(190, "Wrong tape mounted, release and try mount.\n");
348          release = true;
349          do_mount = true;
350       } else {
351          /*
352           * At this point, the correct tape is already mounted, so
353           *   we do not need to do mount_next_write_volume(), unless
354           *   we need to recycle the tape.
355           */
356           recycle = strcmp(dcr->VolCatInfo.VolCatStatus, "Recycle") == 0;
357           Dmsg1(190, "Correct tape mounted. recycle=%d\n", recycle);
358           if (recycle && dev->num_writers != 0) {
359              Jmsg(jcr, M_FATAL, 0, _("Cannot recycle volume \"%s\""
360                   " on device %s because it is in use by another job.\n"),
361                   dev->VolHdr.VolumeName, dev->print_name());
362              goto get_out;
363           }
364           if (dev->num_writers == 0) {
365              memcpy(&dev->VolCatInfo, &dcr->VolCatInfo, sizeof(dev->VolCatInfo));
366           }
367       }
368    } else {
369       /* Not already in append mode, so mount the device */
370       Dmsg0(190, "Not in append mode, try mount.\n");
371       ASSERT(dev->num_writers == 0);
372       do_mount = true;
373    }
374
375    if (do_mount || recycle) {
376       Dmsg0(190, "Do mount_next_write_vol\n");
377       bool mounted = mount_next_write_volume(dcr, release);
378       if (!mounted) {
379          if (!job_canceled(jcr)) {
380             /* Reduce "noise" -- don't print if job canceled */
381             Jmsg(jcr, M_FATAL, 0, _("Could not ready device %s for append.\n"),
382                dev->print_name());
383             Dmsg1(200, "Could not ready device %s for append.\n", 
384                dev->print_name());
385          }
386          goto get_out;
387       }
388       Dmsg2(190, "Output pos=%u:%u\n", dcr->dev->file, dcr->dev->block_num);
389    }
390
391    dev->num_writers++;                /* we are now a writer */
392    if (jcr->NumVolumes == 0) {
393       jcr->NumVolumes = 1;
394    }
395    P(dev->mutex);
396    if (dcr->reserved_device) {
397       dev->reserved_device--;
398       Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
399       dcr->reserved_device = false;
400    }
401    V(dev->mutex);
402    dev->unblock();
403    return dcr;
404
405 /*
406  * Error return
407  */
408 get_out:
409    P(dev->mutex);
410    if (dcr->reserved_device) {
411       dev->reserved_device--;
412       Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
413       dcr->reserved_device = false;
414    }
415    V(dev->mutex);
416    dev->unblock();
417    return NULL;
418 }
419
420
421 /*
422  * This job is done, so release the device. From a Unix standpoint,
423  *  the device remains open.
424  *
425  */
426 bool release_device(DCR *dcr)
427 {
428    JCR *jcr = dcr->jcr;
429    DEVICE *dev = dcr->dev;
430    bool ok = true;
431
432    lock_device(dev);
433    Dmsg2(100, "release_device device %s is %s\n", dev->print_name(), dev->is_tape()?"tape":"disk");
434
435    /* if device is reserved, job never started, so release the reserve here */
436    if (dcr->reserved_device) {
437       dev->reserved_device--;
438       Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
439       dcr->reserved_device = false;
440    }
441
442    if (dev->can_read()) {
443       dev->clear_read();              /* clear read bit */
444
445       /******FIXME**** send read volume usage statistics to director */
446
447    } else if (dev->num_writers > 0) {
448       /* 
449        * Note if WEOT is set, we are at the end of the tape
450        *   and may not be positioned correctly, so the
451        *   job_media_record and update_vol_info have already been
452        *   done, which means we skip them here.
453        */
454       dev->num_writers--;
455       Dmsg1(100, "There are %d writers in release_device\n", dev->num_writers);
456       if (dev->is_labeled()) {
457          Dmsg0(100, "dir_create_jobmedia_record. Release\n");
458          if (!dev->at_weot() && !dir_create_jobmedia_record(dcr)) {
459             Jmsg(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"),
460                dcr->VolCatInfo.VolCatName, jcr->Job);
461          }
462          /* If no more writers, write an EOF */
463          if (!dev->num_writers && dev->can_write()) {
464             dev->weof(1);
465             write_ansi_ibm_labels(dcr, ANSI_EOF_LABEL, dev->VolHdr.VolumeName);
466          }
467          if (!dev->at_weot()) {
468             dev->VolCatInfo.VolCatFiles = dev->file;   /* set number of files */
469             dev->VolCatInfo.VolCatJobs++;              /* increment number of jobs */
470             /* Note! do volume update before close, which zaps VolCatInfo */
471             Dmsg0(100, "dir_update_vol_info. Release0\n");
472             dir_update_volume_info(dcr, false); /* send Volume info to Director */
473          }
474       }
475
476    } else {
477       /*                
478        * If we reach here, it is most likely because the job
479        *   has failed, since the device is not in read mode and
480        *   there are no writers. It was probably reserved.
481        */
482    }
483
484    /* If no writers, close if file or !CAP_ALWAYS_OPEN */
485    if (dev->num_writers == 0 && (!dev->is_tape() || !dev_cap(dev, CAP_ALWAYSOPEN))) {
486       dev->close();
487    }
488
489    /* Fire off Alert command and include any output */
490    if (!job_canceled(jcr) && dcr->device->alert_command) {
491       POOLMEM *alert;
492       int status = 1;
493       BPIPE *bpipe;
494       char line[MAXSTRING];
495       alert = get_pool_memory(PM_FNAME);
496       alert = edit_device_codes(dcr, alert, dcr->device->alert_command, "");
497       bpipe = open_bpipe(alert, 0, "r");
498       if (bpipe) {
499          while (fgets(line, sizeof(line), bpipe->rfd)) {
500             Jmsg(jcr, M_ALERT, 0, _("Alert: %s"), line);
501          }
502          status = close_bpipe(bpipe);
503       } else {
504          status = errno;
505       }
506       if (status != 0) {
507          berrno be;
508          Jmsg(jcr, M_ALERT, 0, _("3997 Bad alert command: %s: ERR=%s.\n"),
509               alert, be.strerror(status));
510       }
511
512       Dmsg1(400, "alert status=%d\n", status);
513       free_pool_memory(alert);
514    }
515    unlock_device(dev);
516    if (jcr->read_dcr == dcr) {
517       jcr->read_dcr = NULL;
518    }
519    if (jcr->dcr == dcr) {
520       jcr->dcr = NULL;
521    }
522    free_dcr(dcr);
523    return ok;
524 }
525
526 /*
527  * Create a new Device Control Record and attach
528  *   it to the device (if this is a real job).
529  */
530 DCR *new_dcr(JCR *jcr, DEVICE *dev)
531 {
532    DCR *dcr = (DCR *)malloc(sizeof(DCR));
533    memset(dcr, 0, sizeof(DCR));
534    dcr->jcr = jcr;
535    if (dev) {
536       dcr->dev = dev;
537       dcr->device = dev->device;
538       dcr->block = new_block(dev);
539       dcr->rec = new_record();
540       dcr->max_job_spool_size = dev->device->max_job_spool_size;
541       /* Attach this dcr only if dev is initialized */
542       if (dev->fd != 0 && jcr && jcr->JobType != JT_SYSTEM) {
543          dev->attached_dcrs->append(dcr);  /* attach dcr to device */
544 //       jcr->dcrs->append(dcr);         /* put dcr in list for Job */
545       }
546    }
547    dcr->spool_fd = -1;
548    return dcr;
549 }
550
551 /*
552  * Search the dcrs list for the given dcr. If it is found,
553  *  as it should be, then remove it. Also zap the jcr pointer
554  *  to the dcr if it is the same one.
555  */
556 #ifdef needed
557 static void remove_dcr_from_dcrs(DCR *dcr)
558 {
559    JCR *jcr = dcr->jcr;
560    if (jcr->dcrs) {
561       int i = 0;
562       DCR *ldcr;
563       int num = jcr->dcrs->size();
564       for (i=0; i < num; i++) {
565          ldcr = (DCR *)jcr->dcrs->get(i);
566          if (ldcr == dcr) {
567             jcr->dcrs->remove(i);
568             if (jcr->dcr == dcr) {
569                jcr->dcr = NULL;
570             }
571          }
572       }
573    }
574 }
575 #endif
576
577 static void detach_dcr_from_dev(DCR *dcr)
578 {
579    DEVICE *dev = dcr->dev;
580
581    if (dcr->reserved_device) {
582       dcr->reserved_device = false;
583       lock_device(dev);
584       dev->reserved_device--;
585       Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
586       dcr->reserved_device = false;
587       /* If we set read mode in reserving, remove it */
588       if (dev->can_read()) {
589          dev->clear_read();
590       }
591       if (dev->num_writers < 0) {
592          Jmsg1(dcr->jcr, M_ERROR, 0, _("Hey! num_writers=%d!!!!\n"), dev->num_writers);
593          dev->num_writers = 0;
594       }
595       unlock_device(dev);
596    }
597
598    /* Detach this dcr only if the dev is initialized */
599    if (dcr->dev->fd != 0 && dcr->jcr && dcr->jcr->JobType != JT_SYSTEM) {
600       dcr->dev->attached_dcrs->remove(dcr);  /* detach dcr from device */
601 //    remove_dcr_from_dcrs(dcr);      /* remove dcr from jcr list */
602    }
603    free_unused_volume(dcr);           /* free unused vols attached to this dcr */
604    pthread_cond_broadcast(&dcr->dev->wait_next_vol);
605    pthread_cond_broadcast(&wait_device_release);
606 }
607
608 /*
609  * Free up all aspects of the given dcr -- i.e. dechain it,
610  *  release allocated memory, zap pointers, ...
611  */
612 void free_dcr(DCR *dcr)
613 {
614
615    detach_dcr_from_dev(dcr);
616
617    if (dcr->block) {
618       free_block(dcr->block);
619    }
620    if (dcr->rec) {
621       free_record(dcr->rec);
622    }
623    if (dcr->jcr) {
624       dcr->jcr->dcr = NULL;
625    }
626    free(dcr);
627 }