7dba54e398e1c167fd39bcbb50926ec88a1aa2dc
[multipath-tools/.git] / multipath / main.c
1 /*
2  * Soft:        multipath device mapper target autoconfig
3  *
4  * Version:     $Id: main.h,v 0.0.1 2003/09/18 15:13:38 cvaroqui Exp $
5  *
6  * Author:      Christophe Varoqui
7  *
8  *              This program is distributed in the hope that it will be useful,
9  *              but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11  *              See the GNU General Public License for more details.
12  *
13  *              This program is free software; you can redistribute it and/or
14  *              modify it under the terms of the GNU General Public License
15  *              as published by the Free Software Foundation; either version
16  *              2 of the License, or (at your option) any later version.
17  *
18  * Copyright (c) 2003, 2004, 2005 Christophe Varoqui
19  * Copyright (c) 2005 Benjamin Marzinski, Redhat
20  * Copyright (c) 2005 Kiyoshi Ueda, NEC
21  * Copyright (c) 2005 Patrick Caulfield, Redhat
22  * Copyright (c) 2005 Edward Goggin, EMC
23  */
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <ctype.h>
30 #include <libudev.h>
31 #include <syslog.h>
32
33 #include "checkers.h"
34 #include "prio.h"
35 #include "vector.h"
36 #include <memory.h>
37 #include <libdevmapper.h>
38 #include "devmapper.h"
39 #include "util.h"
40 #include "defaults.h"
41 #include "config.h"
42 #include "structs.h"
43 #include "structs_vec.h"
44 #include "dmparser.h"
45 #include "sysfs.h"
46 #include "blacklist.h"
47 #include "discovery.h"
48 #include "debug.h"
49 #include "switchgroup.h"
50 #include "print.h"
51 #include "alias.h"
52 #include "configure.h"
53 #include "pgpolicies.h"
54 #include "version.h"
55 #include <errno.h>
56 #include <sys/time.h>
57 #include <sys/resource.h>
58 #include "wwids.h"
59 #include "uxsock.h"
60 #include "mpath_cmd.h"
61
62 int logsink;
63 struct udev *udev;
64 struct config *multipath_conf;
65
66 struct config *get_multipath_config(void)
67 {
68         return multipath_conf;
69 }
70
71 void put_multipath_config(struct config *conf)
72 {
73         /* Noop for now */
74 }
75
76 void rcu_register_thread_memb(void) {}
77
78 void rcu_unregister_thread_memb(void) {}
79
80 static int
81 filter_pathvec (vector pathvec, char * refwwid)
82 {
83         int i;
84         struct path * pp;
85
86         if (!refwwid || !strlen(refwwid))
87                 return 0;
88
89         vector_foreach_slot (pathvec, pp, i) {
90                 if (strncmp(pp->wwid, refwwid, WWID_SIZE) != 0) {
91                         condlog(3, "skip path %s : out of scope", pp->dev);
92                         free_path(pp);
93                         vector_del_slot(pathvec, i);
94                         i--;
95                 }
96         }
97         return 0;
98 }
99
100 static void
101 usage (char * progname)
102 {
103         fprintf (stderr, VERSION_STRING);
104         fprintf (stderr, "Usage:\n");
105         fprintf (stderr, "  %s [-a|-c|-w|-W] [-d] [-r] [-i] [-v lvl] [-p pol] [-b fil] [-q] [dev]\n", progname);
106         fprintf (stderr, "  %s -l|-ll|-f [-v lvl] [-b fil] [dev]\n", progname);
107         fprintf (stderr, "  %s -F [-v lvl]\n", progname);
108         fprintf (stderr, "  %s -t\n", progname);
109         fprintf (stderr, "  %s -h\n", progname);
110         fprintf (stderr,
111                 "\n"
112                 "Where:\n"
113                 "  -h      print this usage text\n"
114                 "  -l      show multipath topology (sysfs and DM info)\n"
115                 "  -ll     show multipath topology (maximum info)\n"
116                 "  -f      flush a multipath device map\n"
117                 "  -F      flush all multipath device maps\n"
118                 "  -a      add a device wwid to the wwids file\n"
119                 "  -c      check if a device should be a path in a multipath device\n"
120                 "  -q      allow queue_if_no_path when multipathd is not running\n"
121                 "  -d      dry run, do not create or update devmaps\n"
122                 "  -t      dump internal hardware table\n"
123                 "  -r      force devmap reload\n"
124                 "  -i      ignore wwids file\n"
125                 "  -B      treat the bindings file as read only\n"
126                 "  -b fil  bindings file location\n"
127                 "  -w      remove a device from the wwids file\n"
128                 "  -W      reset the wwids file include only the current devices\n"
129                 "  -p pol  force all maps to specified path grouping policy :\n"
130                 "          . failover            one path per priority group\n"
131                 "          . multibus            all paths in one priority group\n"
132                 "          . group_by_serial     one priority group per serial\n"
133                 "          . group_by_prio       one priority group per priority lvl\n"
134                 "          . group_by_node_name  one priority group per target node\n"
135                 "  -v lvl  verbosity level\n"
136                 "          . 0 no output\n"
137                 "          . 1 print created devmap names only\n"
138                 "          . 2 default verbosity\n"
139                 "          . 3 print debug information\n"
140                 "  dev     action limited to:\n"
141                 "          . multipath named 'dev' (ex: mpath0) or\n"
142                 "          . multipath whose wwid is 'dev' (ex: 60051..)\n"
143                 "          . multipath including the path named 'dev' (ex: /dev/sda)\n"
144                 "          . multipath including the path with maj:min 'dev' (ex: 8:0)\n"
145                 );
146
147 }
148
149 static int
150 update_paths (struct multipath * mpp)
151 {
152         int i, j;
153         struct pathgroup * pgp;
154         struct path * pp;
155         struct config *conf;
156
157         if (!mpp->pg)
158                 return 0;
159
160         vector_foreach_slot (mpp->pg, pgp, i) {
161                 if (!pgp->paths)
162                         continue;
163
164                 vector_foreach_slot (pgp->paths, pp, j) {
165                         if (!strlen(pp->dev)) {
166                                 if (devt2devname(pp->dev, FILE_NAME_SIZE,
167                                                  pp->dev_t)) {
168                                         /*
169                                          * path is not in sysfs anymore
170                                          */
171                                         pp->chkrstate = pp->state = PATH_DOWN;
172                                         continue;
173                                 }
174                                 pp->mpp = mpp;
175                                 conf = get_multipath_config();
176                                 if (pathinfo(pp, conf, DI_ALL))
177                                         pp->state = PATH_UNCHECKED;
178                                 put_multipath_config(conf);
179                                 continue;
180                         }
181                         pp->mpp = mpp;
182                         if (pp->state == PATH_UNCHECKED ||
183                             pp->state == PATH_WILD) {
184                                 conf = get_multipath_config();
185                                 if (pathinfo(pp, conf, DI_CHECKER))
186                                         pp->state = PATH_UNCHECKED;
187                                 put_multipath_config(conf);
188                         }
189
190                         if (pp->priority == PRIO_UNDEF) {
191                                 conf = get_multipath_config();
192                                 if (pathinfo(pp, conf, DI_PRIO))
193                                         pp->priority = PRIO_UNDEF;
194                                 put_multipath_config(conf);
195                         }
196                 }
197         }
198         return 0;
199 }
200
201 static int
202 get_dm_mpvec (enum mpath_cmds cmd, vector curmp, vector pathvec, char * refwwid)
203 {
204         int i;
205         struct multipath * mpp;
206         char params[PARAMS_SIZE], status[PARAMS_SIZE];
207
208         if (dm_get_maps(curmp))
209                 return 1;
210
211         vector_foreach_slot (curmp, mpp, i) {
212                 /*
213                  * discard out of scope maps
214                  */
215                 if (mpp->wwid && refwwid &&
216                     strncmp(mpp->wwid, refwwid, WWID_SIZE)) {
217                         condlog(3, "skip map %s: out of scope", mpp->alias);
218                         free_multipath(mpp, KEEP_PATHS);
219                         vector_del_slot(curmp, i);
220                         i--;
221                         continue;
222                 }
223
224                 if (cmd == CMD_VALID_PATH)
225                         continue;
226
227                 dm_get_map(mpp->alias, &mpp->size, params);
228                 condlog(3, "params = %s", params);
229                 dm_get_status(mpp->alias, status);
230                 condlog(3, "status = %s", status);
231
232                 disassemble_map(pathvec, params, mpp, 0);
233
234                 /*
235                  * disassemble_map() can add new paths to pathvec.
236                  * If not in "fast list mode", we need to fetch information
237                  * about them
238                  */
239                 if (cmd != CMD_LIST_SHORT)
240                         update_paths(mpp);
241
242                 if (cmd == CMD_LIST_LONG)
243                         mpp->bestpg = select_path_group(mpp);
244
245                 disassemble_status(status, mpp);
246
247                 if (cmd == CMD_LIST_SHORT ||
248                     cmd == CMD_LIST_LONG) {
249                         struct config *conf = get_multipath_config();
250                         print_multipath_topology(mpp, conf->verbosity);
251                         put_multipath_config(conf);
252                 }
253
254                 if (cmd == CMD_CREATE)
255                         reinstate_paths(mpp);
256         }
257         return 0;
258 }
259
260
261 /*
262  * Return value:
263  *  -1: Retry
264  *   0: Success
265  *   1: Failure
266  */
267 static int
268 configure (enum mpath_cmds cmd, enum devtypes dev_type, char *devpath)
269 {
270         vector curmp = NULL;
271         vector pathvec = NULL;
272         struct vectors vecs;
273         int r = 1;
274         int di_flag = 0;
275         char * refwwid = NULL;
276         char * dev = NULL;
277         struct config *conf;
278
279         /*
280          * allocate core vectors to store paths and multipaths
281          */
282         curmp = vector_alloc();
283         pathvec = vector_alloc();
284
285         if (!curmp || !pathvec) {
286                 condlog(0, "can not allocate memory");
287                 goto out;
288         }
289         vecs.pathvec = pathvec;
290         vecs.mpvec = curmp;
291
292         dev = convert_dev(devpath, (dev_type == DEV_DEVNODE));
293
294         /*
295          * if we have a blacklisted device parameter, exit early
296          */
297         conf = get_multipath_config();
298         if (dev && (dev_type == DEV_DEVNODE ||
299                     dev_type == DEV_UEVENT) &&
300             cmd != CMD_REMOVE_WWID &&
301             (filter_devnode(conf->blist_devnode,
302                             conf->elist_devnode, dev) > 0)) {
303                 if (cmd == CMD_VALID_PATH)
304                         printf("%s is not a valid multipath device path\n",
305                                devpath);
306                 put_multipath_config(conf);
307                 goto out;
308         }
309         put_multipath_config(conf);
310         /*
311          * scope limiting must be translated into a wwid
312          * failing the translation is fatal (by policy)
313          */
314         if (devpath) {
315                 int failed = get_refwwid(cmd, devpath, dev_type,
316                                          pathvec, &refwwid);
317                 if (!refwwid) {
318                         condlog(4, "%s: failed to get wwid", devpath);
319                         if (failed == 2 && cmd == CMD_VALID_PATH)
320                                 printf("%s is not a valid multipath device path\n", devpath);
321                         else
322                                 condlog(3, "scope is null");
323                         goto out;
324                 }
325                 if (cmd == CMD_REMOVE_WWID) {
326                         r = remove_wwid(refwwid);
327                         if (r == 0)
328                                 printf("wwid '%s' removed\n", refwwid);
329                         else if (r == 1) {
330                                 printf("wwid '%s' not in wwids file\n",
331                                         refwwid);
332                                 r = 0;
333                         }
334                         goto out;
335                 }
336                 if (cmd == CMD_ADD_WWID) {
337                         r = remember_wwid(refwwid);
338                         if (r == 0)
339                                 printf("wwid '%s' added\n", refwwid);
340                         else
341                                 printf("failed adding '%s' to wwids file\n",
342                                        refwwid);
343                         goto out;
344                 }
345                 condlog(3, "scope limited to %s", refwwid);
346                 /* If you are ignoring the wwids file and find_multipaths is
347                  * set, you need to actually check if there are two available
348                  * paths to determine if this path should be multipathed. To
349                  * do this, we put off the check until after discovering all
350                  * the paths */
351                 if (cmd == CMD_VALID_PATH &&
352                     (!conf->find_multipaths || !conf->ignore_wwids)) {
353                         if (conf->ignore_wwids ||
354                             check_wwids_file(refwwid, 0) == 0)
355                                 r = 0;
356
357                         printf("%s %s a valid multipath device path\n",
358                                devpath, r == 0 ? "is" : "is not");
359                         goto out;
360                 }
361         }
362
363         /*
364          * get a path list
365          */
366         if (devpath)
367                 di_flag = DI_WWID;
368
369         if (cmd == CMD_LIST_LONG)
370                 /* extended path info '-ll' */
371                 di_flag |= DI_SYSFS | DI_CHECKER;
372         else if (cmd == CMD_LIST_SHORT)
373                 /* minimum path info '-l' */
374                 di_flag |= DI_SYSFS;
375         else
376                 /* maximum info */
377                 di_flag = DI_ALL;
378
379         if (path_discovery(pathvec, di_flag) < 0)
380                 goto out;
381
382         if (conf->verbosity > 2)
383                 print_all_paths(pathvec, 1);
384
385         get_path_layout(pathvec, 0);
386
387         if (get_dm_mpvec(cmd, curmp, pathvec, refwwid))
388                 goto out;
389
390         filter_pathvec(pathvec, refwwid);
391
392
393         if (cmd == CMD_VALID_PATH) {
394                 /* This only happens if find_multipaths and
395                  * ignore_wwids is set.
396                  * If there is currently a multipath device matching
397                  * the refwwid, or there is more than one path matching
398                  * the refwwid, then the path is valid */
399                 if (VECTOR_SIZE(curmp) != 0 || VECTOR_SIZE(pathvec) > 1)
400                         r = 0;
401                 printf("%s %s a valid multipath device path\n",
402                        devpath, r == 0 ? "is" : "is not");
403                 goto out;
404         }
405
406         if (cmd != CMD_CREATE && cmd != CMD_DRY_RUN) {
407                 r = 0;
408                 goto out;
409         }
410
411         /*
412          * core logic entry point
413          */
414         r = coalesce_paths(&vecs, NULL, refwwid,
415                            conf->force_reload, cmd);
416
417 out:
418         if (refwwid)
419                 FREE(refwwid);
420
421         free_multipathvec(curmp, KEEP_PATHS);
422         free_pathvec(pathvec, FREE_PATHS);
423
424         return r;
425 }
426
427 static int
428 dump_config (struct config *conf)
429 {
430         char * c, * tmp = NULL;
431         char * reply;
432         unsigned int maxlen = 256;
433         int again = 1;
434
435         reply = MALLOC(maxlen);
436
437         while (again) {
438                 if (!reply) {
439                         if (tmp)
440                                 free(tmp);
441                         return 1;
442                 }
443                 c = tmp = reply;
444                 c += snprint_defaults(conf, c, reply + maxlen - c);
445                 again = ((c - reply) == maxlen);
446                 if (again) {
447                         reply = REALLOC(reply, maxlen *= 2);
448                         continue;
449                 }
450                 c += snprint_blacklist(conf, c, reply + maxlen - c);
451                 again = ((c - reply) == maxlen);
452                 if (again) {
453                         reply = REALLOC(reply, maxlen *= 2);
454                         continue;
455                 }
456                 c += snprint_blacklist_except(conf, c, reply + maxlen - c);
457                 again = ((c - reply) == maxlen);
458                 if (again) {
459                         reply = REALLOC(reply, maxlen *= 2);
460                         continue;
461                 }
462                 c += snprint_hwtable(conf, c, reply + maxlen - c, conf->hwtable);
463                 again = ((c - reply) == maxlen);
464                 if (again) {
465                         reply = REALLOC(reply, maxlen *= 2);
466                         continue;
467                 }
468                 c += snprint_overrides(conf, c, reply + maxlen - c,
469                                        conf->overrides);
470                 again = ((c - reply) == maxlen);
471                 if (again) {
472                         reply = REALLOC(reply, maxlen *= 2);
473                         continue;
474                 }
475                 if (VECTOR_SIZE(conf->mptable) > 0) {
476                         c += snprint_mptable(conf, c, reply + maxlen - c,
477                                              conf->mptable);
478                         again = ((c - reply) == maxlen);
479                         if (again)
480                                 reply = REALLOC(reply, maxlen *= 2);
481                 }
482         }
483
484         printf("%s", reply);
485         FREE(reply);
486         return 0;
487 }
488
489 static int
490 get_dev_type(char *dev) {
491         struct stat buf;
492         int i;
493
494         if (stat(dev, &buf) == 0 && S_ISBLK(buf.st_mode)) {
495                 if (dm_is_dm_major(major(buf.st_rdev)))
496                         return DEV_DEVMAP;
497                 return DEV_DEVNODE;
498         }
499         else if (sscanf(dev, "%d:%d", &i, &i) == 2)
500                 return DEV_DEVT;
501         else if (valid_alias(dev))
502                 return DEV_DEVMAP;
503         return DEV_NONE;
504 }
505
506 int
507 main (int argc, char *argv[])
508 {
509         int arg;
510         extern char *optarg;
511         extern int optind;
512         int r = 1;
513         enum mpath_cmds cmd = CMD_CREATE;
514         enum devtypes dev_type;
515         char *dev = NULL;
516         struct config *conf;
517
518         udev = udev_new();
519         logsink = 0;
520         conf = load_config(DEFAULT_CONFIGFILE);
521         if (!conf)
522                 exit(1);
523         multipath_conf = conf;
524         while ((arg = getopt(argc, argv, ":adchl::FfM:v:p:b:BritquwW")) != EOF ) {
525                 switch(arg) {
526                 case 1: printf("optarg : %s\n",optarg);
527                         break;
528                 case 'v':
529                         if (sizeof(optarg) > sizeof(char *) ||
530                             !isdigit(optarg[0])) {
531                                 usage (argv[0]);
532                                 exit(1);
533                         }
534
535                         conf->verbosity = atoi(optarg);
536                         break;
537                 case 'b':
538                         conf->bindings_file = strdup(optarg);
539                         break;
540                 case 'B':
541                         conf->bindings_read_only = 1;
542                         break;
543                 case 'q':
544                         conf->allow_queueing = 1;
545                         break;
546                 case 'c':
547                         cmd = CMD_VALID_PATH;
548                         break;
549                 case 'd':
550                         if (cmd == CMD_CREATE)
551                                 cmd = CMD_DRY_RUN;
552                         break;
553                 case 'f':
554                         conf->remove = FLUSH_ONE;
555                         break;
556                 case 'F':
557                         conf->remove = FLUSH_ALL;
558                         break;
559                 case 'l':
560                         if (optarg && !strncmp(optarg, "l", 1))
561                                 cmd = CMD_LIST_LONG;
562                         else
563                                 cmd = CMD_LIST_SHORT;
564
565                         break;
566                 case 'M':
567 #if _DEBUG_
568                         debug = atoi(optarg);
569 #endif
570                         break;
571                 case 'p':
572                         conf->pgpolicy_flag = get_pgpolicy_id(optarg);
573                         if (conf->pgpolicy_flag == IOPOLICY_UNDEF) {
574                                 printf("'%s' is not a valid policy\n", optarg);
575                                 usage(argv[0]);
576                                 exit(1);
577                         }
578                         break;
579                 case 'r':
580                         conf->force_reload = 1;
581                         break;
582                 case 'i':
583                         conf->ignore_wwids = 1;
584                         break;
585                 case 't':
586                         r = dump_config(conf);
587                         goto out_free_config;
588                 case 'h':
589                         usage(argv[0]);
590                         exit(0);
591                 case 'u':
592                         cmd = CMD_VALID_PATH;
593                         dev_type = DEV_UEVENT;
594                         break;
595                 case 'w':
596                         cmd = CMD_REMOVE_WWID;
597                         break;
598                 case 'W':
599                         cmd = CMD_RESET_WWIDS;
600                         break;
601                 case 'a':
602                         cmd = CMD_ADD_WWID;
603                         break;
604                 case ':':
605                         fprintf(stderr, "Missing option argument\n");
606                         usage(argv[0]);
607                         exit(1);
608                 case '?':
609                         fprintf(stderr, "Unknown switch: %s\n", optarg);
610                         usage(argv[0]);
611                         exit(1);
612                 default:
613                         usage(argv[0]);
614                         exit(1);
615                 }
616         }
617
618         if (getuid() != 0) {
619                 fprintf(stderr, "need to be root\n");
620                 exit(1);
621         }
622
623         dm_init(conf->verbosity);
624         if (dm_prereq())
625                 exit(1);
626         dm_drv_version(conf->version, TGT_MPATH);
627         dm_udev_set_sync_support(1);
628
629         if (optind < argc) {
630                 dev = MALLOC(FILE_NAME_SIZE);
631
632                 if (!dev)
633                         goto out;
634
635                 strncpy(dev, argv[optind], FILE_NAME_SIZE);
636                 if (dev_type != DEV_UEVENT)
637                         dev_type = get_dev_type(dev);
638                 if (dev_type == DEV_NONE) {
639                         condlog(0, "'%s' is not a valid argument\n", dev);
640                         goto out;
641                 }
642         }
643         if (dev_type == DEV_UEVENT) {
644                 openlog("multipath", 0, LOG_DAEMON);
645                 setlogmask(LOG_UPTO(conf->verbosity + 3));
646                 logsink = 1;
647         }
648
649         if (conf->max_fds) {
650                 struct rlimit fd_limit;
651
652                 fd_limit.rlim_cur = conf->max_fds;
653                 fd_limit.rlim_max = conf->max_fds;
654                 if (setrlimit(RLIMIT_NOFILE, &fd_limit) < 0)
655                         condlog(0, "can't set open fds limit to %d : %s",
656                                 conf->max_fds, strerror(errno));
657         }
658
659         if (init_checkers(conf->multipath_dir)) {
660                 condlog(0, "failed to initialize checkers");
661                 goto out;
662         }
663         if (init_prio(conf->multipath_dir)) {
664                 condlog(0, "failed to initialize prioritizers");
665                 goto out;
666         }
667
668         if (cmd == CMD_VALID_PATH &&
669             (!dev || dev_type == DEV_DEVMAP)) {
670                 condlog(0, "the -c option requires a path to check");
671                 goto out;
672         }
673         if (cmd == CMD_VALID_PATH &&
674             dev_type == DEV_UEVENT) {
675                 int fd;
676
677                 fd = mpath_connect();
678                 if (fd == -1) {
679                         printf("%s is not a valid multipath device path\n",
680                                 dev);
681                         goto out;
682                 }
683                 mpath_disconnect(fd);
684         }
685         if (cmd == CMD_REMOVE_WWID && !dev) {
686                 condlog(0, "the -w option requires a device");
687                 goto out;
688         }
689         if (cmd == CMD_RESET_WWIDS) {
690                 struct multipath * mpp;
691                 int i;
692                 vector curmp;
693
694                 curmp = vector_alloc();
695                 if (!curmp) {
696                         condlog(0, "can't allocate memory for mp list");
697                         goto out;
698                 }
699                 if (dm_get_maps(curmp) == 0)
700                         r = replace_wwids(curmp);
701                 if (r == 0)
702                         printf("successfully reset wwids\n");
703                 vector_foreach_slot_backwards(curmp, mpp, i) {
704                         vector_del_slot(curmp, i);
705                         free_multipath(mpp, KEEP_PATHS);
706                 }
707                 vector_free(curmp);
708                 goto out;
709         }
710         if (conf->remove == FLUSH_ONE) {
711                 if (dev_type == DEV_DEVMAP) {
712                         r = dm_suspend_and_flush_map(dev);
713                 } else
714                         condlog(0, "must provide a map name to remove");
715
716                 goto out;
717         }
718         else if (conf->remove == FLUSH_ALL) {
719                 r = dm_flush_maps();
720                 goto out;
721         }
722         while ((r = configure(cmd, dev_type, dev)) < 0)
723                 condlog(3, "restart multipath configuration process");
724
725 out:
726         dm_lib_release();
727         dm_lib_exit();
728
729         cleanup_prio();
730         cleanup_checkers();
731
732         if (dev_type == DEV_UEVENT)
733                 closelog();
734
735 out_free_config:
736         /*
737          * Freeing config must be done after dm_lib_exit(), because
738          * the logging function (dm_write_log()), which is called there,
739          * references the config.
740          */
741         free_config(conf);
742         conf = NULL;
743         udev_unref(udev);
744         if (dev)
745                 FREE(dev);
746 #ifdef _DEBUG_
747         dbg_free_final(NULL);
748 #endif
749         return r;
750 }