]> git.ipfire.org Git - thirdparty/glibc.git/blob - iconv/gconv_db.c
Update.
[thirdparty/glibc.git] / iconv / gconv_db.c
1 /* Provide access to the collection of available transformation modules.
2 Copyright (C) 1997,98,99,2000,2001,2002,2003 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
5
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA. */
20
21 #include <limits.h>
22 #include <search.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/param.h>
26 #include <bits/libc-lock.h>
27
28 #include <dlfcn.h>
29 #include <gconv_int.h>
30
31
32 /* Simple data structure for alias mapping. We have two names, `from'
33 and `to'. */
34 void *__gconv_alias_db;
35
36 /* Array with available modules. */
37 struct gconv_module *__gconv_modules_db;
38
39 /* We modify global data. */
40 __libc_lock_define_initialized (static, lock)
41
42
43 /* Provide access to module database. */
44 struct gconv_module *
45 __gconv_get_modules_db (void)
46 {
47 return __gconv_modules_db;
48 }
49
50 void *
51 __gconv_get_alias_db (void)
52 {
53 return __gconv_alias_db;
54 }
55
56
57 /* Function for searching alias. */
58 int
59 __gconv_alias_compare (const void *p1, const void *p2)
60 {
61 const struct gconv_alias *s1 = (const struct gconv_alias *) p1;
62 const struct gconv_alias *s2 = (const struct gconv_alias *) p2;
63 return strcmp (s1->fromname, s2->fromname);
64 }
65
66
67 /* To search for a derivation we create a list of intermediate steps.
68 Each element contains a pointer to the element which precedes it
69 in the derivation order. */
70 struct derivation_step
71 {
72 const char *result_set;
73 size_t result_set_len;
74 int cost_lo;
75 int cost_hi;
76 struct gconv_module *code;
77 struct derivation_step *last;
78 struct derivation_step *next;
79 };
80
81 #define NEW_STEP(result, hi, lo, module, last_mod) \
82 ({ struct derivation_step *newp = alloca (sizeof (struct derivation_step)); \
83 newp->result_set = result; \
84 newp->result_set_len = strlen (result); \
85 newp->cost_hi = hi; \
86 newp->cost_lo = lo; \
87 newp->code = module; \
88 newp->last = last_mod; \
89 newp->next = NULL; \
90 newp; })
91
92
93 /* If a specific transformation is used more than once we should not need
94 to start looking for it again. Instead cache each successful result. */
95 struct known_derivation
96 {
97 const char *from;
98 const char *to;
99 struct __gconv_step *steps;
100 size_t nsteps;
101 };
102
103 /* Compare function for database of found derivations. */
104 static int
105 derivation_compare (const void *p1, const void *p2)
106 {
107 const struct known_derivation *s1 = (const struct known_derivation *) p1;
108 const struct known_derivation *s2 = (const struct known_derivation *) p2;
109 int result;
110
111 result = strcmp (s1->from, s2->from);
112 if (result == 0)
113 result = strcmp (s1->to, s2->to);
114 return result;
115 }
116
117 /* The search tree for known derivations. */
118 static void *known_derivations;
119
120 /* Look up whether given transformation was already requested before. */
121 static int
122 internal_function
123 derivation_lookup (const char *fromset, const char *toset,
124 struct __gconv_step **handle, size_t *nsteps)
125 {
126 struct known_derivation key = { fromset, toset, NULL, 0 };
127 struct known_derivation **result;
128
129 result = __tfind (&key, &known_derivations, derivation_compare);
130
131 if (result == NULL)
132 return __GCONV_NOCONV;
133
134 *handle = (*result)->steps;
135 *nsteps = (*result)->nsteps;
136
137 /* Please note that we return GCONV_OK even if the last search for
138 this transformation was unsuccessful. */
139 return __GCONV_OK;
140 }
141
142 /* Add new derivation to list of known ones. */
143 static void
144 internal_function
145 add_derivation (const char *fromset, const char *toset,
146 struct __gconv_step *handle, size_t nsteps)
147 {
148 struct known_derivation *new_deriv;
149 size_t fromset_len = strlen (fromset) + 1;
150 size_t toset_len = strlen (toset) + 1;
151
152 new_deriv = (struct known_derivation *)
153 malloc (sizeof (struct known_derivation) + fromset_len + toset_len);
154 if (new_deriv != NULL)
155 {
156 new_deriv->from = (char *) (new_deriv + 1);
157 new_deriv->to = memcpy (__mempcpy (new_deriv + 1, fromset, fromset_len),
158 toset, toset_len);
159
160 new_deriv->steps = handle;
161 new_deriv->nsteps = nsteps;
162
163 if (__tsearch (new_deriv, &known_derivations, derivation_compare)
164 == NULL)
165 /* There is some kind of memory allocation problem. */
166 free (new_deriv);
167 }
168 /* Please note that we don't complain if the allocation failed. This
169 is not tragically but in case we use the memory debugging facilities
170 not all memory will be freed. */
171 }
172
173 static void
174 free_derivation (void *p)
175 {
176 struct known_derivation *deriv = (struct known_derivation *) p;
177 size_t cnt;
178
179 for (cnt = 0; cnt < deriv->nsteps; ++cnt)
180 if (deriv->steps[cnt].__counter > 0
181 && deriv->steps[cnt].__end_fct != NULL)
182 DL_CALL_FCT (deriv->steps[cnt].__end_fct, (&deriv->steps[cnt]));
183
184 /* Free the name strings. */
185 free ((char *) deriv->steps[0].__from_name);
186 free ((char *) deriv->steps[deriv->nsteps - 1].__to_name);
187
188 free ((struct __gconv_step *) deriv->steps);
189 free (deriv);
190 }
191
192
193 /* Decrement the reference count for a single step in a steps array. */
194 void
195 internal_function
196 __gconv_release_step (struct __gconv_step *step)
197 {
198 if (--step->__counter == 0)
199 {
200 /* Call the destructor. */
201 if (step->__end_fct != NULL)
202 DL_CALL_FCT (step->__end_fct, (step));
203
204 #ifndef STATIC_GCONV
205 /* Skip builtin modules; they are not reference counted. */
206 if (step->__shlib_handle != NULL)
207 {
208 /* Release the loaded module. */
209 __gconv_release_shlib (step->__shlib_handle);
210 step->__shlib_handle = NULL;
211 }
212 #endif
213 }
214 }
215
216 static int
217 internal_function
218 gen_steps (struct derivation_step *best, const char *toset,
219 const char *fromset, struct __gconv_step **handle, size_t *nsteps)
220 {
221 size_t step_cnt = 0;
222 struct __gconv_step *result;
223 struct derivation_step *current;
224 int status = __GCONV_NOMEM;
225
226 /* First determine number of steps. */
227 for (current = best; current->last != NULL; current = current->last)
228 ++step_cnt;
229
230 result = (struct __gconv_step *) malloc (sizeof (struct __gconv_step)
231 * step_cnt);
232 if (result != NULL)
233 {
234 int failed = 0;
235
236 status = __GCONV_OK;
237 *nsteps = step_cnt;
238 current = best;
239 while (step_cnt-- > 0)
240 {
241 result[step_cnt].__from_name = (step_cnt == 0
242 ? __strdup (fromset)
243 : (char *)current->last->result_set);
244 result[step_cnt].__to_name = (step_cnt + 1 == *nsteps
245 ? __strdup (current->result_set)
246 : result[step_cnt + 1].__from_name);
247
248 result[step_cnt].__counter = 1;
249 result[step_cnt].__data = NULL;
250
251 #ifndef STATIC_GCONV
252 if (current->code->module_name[0] == '/')
253 {
254 /* Load the module, return handle for it. */
255 struct __gconv_loaded_object *shlib_handle =
256 __gconv_find_shlib (current->code->module_name);
257
258 if (shlib_handle == NULL)
259 {
260 failed = 1;
261 break;
262 }
263
264 result[step_cnt].__shlib_handle = shlib_handle;
265 result[step_cnt].__modname = shlib_handle->name;
266 result[step_cnt].__fct = shlib_handle->fct;
267 result[step_cnt].__init_fct = shlib_handle->init_fct;
268 result[step_cnt].__end_fct = shlib_handle->end_fct;
269
270 /* These settings can be overridden by the init function. */
271 result[step_cnt].__btowc_fct = NULL;
272
273 /* Call the init function. */
274 if (result[step_cnt].__init_fct != NULL)
275 {
276 status = DL_CALL_FCT (result[step_cnt].__init_fct,
277 (&result[step_cnt]));
278
279 if (__builtin_expect (status, __GCONV_OK) != __GCONV_OK)
280 {
281 failed = 1;
282 /* Make sure we unload this modules. */
283 --step_cnt;
284 result[step_cnt].__end_fct = NULL;
285 break;
286 }
287 }
288 }
289 else
290 #endif
291 /* It's a builtin transformation. */
292 __gconv_get_builtin_trans (current->code->module_name,
293 &result[step_cnt]);
294
295 current = current->last;
296 }
297
298 if (__builtin_expect (failed, 0) != 0)
299 {
300 /* Something went wrong while initializing the modules. */
301 while (++step_cnt < *nsteps)
302 __gconv_release_step (&result[step_cnt]);
303 free (result);
304 *nsteps = 0;
305 *handle = NULL;
306 if (status == __GCONV_OK)
307 status = __GCONV_NOCONV;
308 }
309 else
310 *handle = result;
311 }
312 else
313 {
314 *nsteps = 0;
315 *handle = NULL;
316 }
317
318 return status;
319 }
320
321
322 #ifndef STATIC_GCONV
323 static int
324 internal_function
325 increment_counter (struct __gconv_step *steps, size_t nsteps)
326 {
327 /* Increment the user counter. */
328 size_t cnt = nsteps;
329 int result = __GCONV_OK;
330
331 while (cnt-- > 0)
332 {
333 struct __gconv_step *step = &steps[cnt];
334
335 if (step->__counter++ == 0)
336 {
337 /* Skip builtin modules. */
338 if (step->__modname != NULL)
339 {
340 /* Reopen a previously used module. */
341 step->__shlib_handle = __gconv_find_shlib (step->__modname);
342 if (step->__shlib_handle == NULL)
343 {
344 /* Oops, this is the second time we use this module
345 (after unloading) and this time loading failed!? */
346 --step->__counter;
347 while (++cnt < nsteps)
348 __gconv_release_step (&steps[cnt]);
349 result = __GCONV_NOCONV;
350 break;
351 }
352
353 /* The function addresses defined by the module may
354 have changed. */
355 step->__fct = step->__shlib_handle->fct;
356 step->__init_fct = step->__shlib_handle->init_fct;
357 step->__end_fct = step->__shlib_handle->end_fct;
358
359 /* These settings can be overridden by the init function. */
360 step->__btowc_fct = NULL;
361 }
362
363 /* Call the init function. */
364 if (step->__init_fct != NULL)
365 DL_CALL_FCT (step->__init_fct, (step));
366 }
367 }
368 return result;
369 }
370 #endif
371
372
373 /* The main function: find a possible derivation from the `fromset' (either
374 the given name or the alias) to the `toset' (again with alias). */
375 static int
376 internal_function
377 find_derivation (const char *toset, const char *toset_expand,
378 const char *fromset, const char *fromset_expand,
379 struct __gconv_step **handle, size_t *nsteps)
380 {
381 struct derivation_step *first, *current, **lastp, *solution = NULL;
382 int best_cost_hi = INT_MAX;
383 int best_cost_lo = INT_MAX;
384 int result;
385
386 /* Look whether an earlier call to `find_derivation' has already
387 computed a possible derivation. If so, return it immediately. */
388 result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
389 handle, nsteps);
390 if (result == __GCONV_OK)
391 {
392 #ifndef STATIC_GCONV
393 result = increment_counter (*handle, *nsteps);
394 #endif
395 return result;
396 }
397
398 /* The task is to find a sequence of transformations, backed by the
399 existing modules - whether builtin or dynamically loadable -,
400 starting at `fromset' (or `fromset_expand') and ending at `toset'
401 (or `toset_expand'), and with minimal cost.
402
403 For computer scientists, this is a shortest path search in the
404 graph where the nodes are all possible charsets and the edges are
405 the transformations listed in __gconv_modules_db.
406
407 For now we use a simple algorithm with quadratic runtime behaviour.
408 A breadth-first search, starting at `fromset' and `fromset_expand'.
409 The list starting at `first' contains all nodes that have been
410 visited up to now, in the order in which they have been visited --
411 excluding the goal nodes `toset' and `toset_expand' which get
412 managed in the list starting at `solution'.
413 `current' walks through the list starting at `first' and looks
414 which nodes are reachable from the current node, adding them to
415 the end of the list [`first' or `solution' respectively] (if
416 they are visited the first time) or updating them in place (if
417 they have have already been visited).
418 In each node of either list, cost_lo and cost_hi contain the
419 minimum cost over any paths found up to now, starting at `fromset'
420 or `fromset_expand', ending at that node. best_cost_lo and
421 best_cost_hi represent the minimum over the elements of the
422 `solution' list. */
423
424 if (fromset_expand != NULL)
425 {
426 first = NEW_STEP (fromset_expand, 0, 0, NULL, NULL);
427 first->next = NEW_STEP (fromset, 0, 0, NULL, NULL);
428 lastp = &first->next->next;
429 }
430 else
431 {
432 first = NEW_STEP (fromset, 0, 0, NULL, NULL);
433 lastp = &first->next;
434 }
435
436 for (current = first; current != NULL; current = current->next)
437 {
438 /* Now match all the available module specifications against the
439 current charset name. If any of them matches check whether
440 we already have a derivation for this charset. If yes, use the
441 one with the lower costs. Otherwise add the new charset at the
442 end.
443
444 The module database is organized in a tree form which allows
445 searching for prefixes. So we search for the first entry with a
446 matching prefix and any other matching entry can be found from
447 this place. */
448 struct gconv_module *node;
449
450 /* Maybe it is not necessary anymore to look for a solution for
451 this entry since the cost is already as high (or higher) as
452 the cost for the best solution so far. */
453 if (current->cost_hi > best_cost_hi
454 || (current->cost_hi == best_cost_hi
455 && current->cost_lo >= best_cost_lo))
456 continue;
457
458 node = __gconv_modules_db;
459 while (node != NULL)
460 {
461 int cmpres = strcmp (current->result_set, node->from_string);
462 if (cmpres == 0)
463 {
464 /* Walk through the list of modules with this prefix and
465 try to match the name. */
466 struct gconv_module *runp;
467
468 /* Check all the modules with this prefix. */
469 runp = node;
470 do
471 {
472 const char *result_set = (strcmp (runp->to_string, "-") == 0
473 ? (toset_expand ?: toset)
474 : runp->to_string);
475 int cost_hi = runp->cost_hi + current->cost_hi;
476 int cost_lo = runp->cost_lo + current->cost_lo;
477 struct derivation_step *step;
478
479 /* We managed to find a derivation. First see whether
480 we have reached one of the goal nodes. */
481 if (strcmp (result_set, toset) == 0
482 || (toset_expand != NULL
483 && strcmp (result_set, toset_expand) == 0))
484 {
485 /* Append to the `solution' list if there
486 is no entry with this name. */
487 for (step = solution; step != NULL; step = step->next)
488 if (strcmp (result_set, step->result_set) == 0)
489 break;
490
491 if (step == NULL)
492 {
493 step = NEW_STEP (result_set,
494 cost_hi, cost_lo,
495 runp, current);
496 step->next = solution;
497 solution = step;
498 }
499 else if (step->cost_hi > cost_hi
500 || (step->cost_hi == cost_hi
501 && step->cost_lo > cost_lo))
502 {
503 /* A better path was found for the node,
504 on the `solution' list. */
505 step->code = runp;
506 step->last = current;
507 step->cost_hi = cost_hi;
508 step->cost_lo = cost_lo;
509 }
510
511 /* Update best_cost accordingly. */
512 if (cost_hi < best_cost_hi
513 || (cost_hi == best_cost_hi
514 && cost_lo < best_cost_lo))
515 {
516 best_cost_hi = cost_hi;
517 best_cost_lo = cost_lo;
518 }
519 }
520 else if (cost_hi < best_cost_hi
521 || (cost_hi == best_cost_hi
522 && cost_lo < best_cost_lo))
523 {
524 /* Append at the end of the `first' list if there
525 is no entry with this name. */
526 for (step = first; step != NULL; step = step->next)
527 if (strcmp (result_set, step->result_set) == 0)
528 break;
529
530 if (step == NULL)
531 {
532 *lastp = NEW_STEP (result_set,
533 cost_hi, cost_lo,
534 runp, current);
535 lastp = &(*lastp)->next;
536 }
537 else if (step->cost_hi > cost_hi
538 || (step->cost_hi == cost_hi
539 && step->cost_lo > cost_lo))
540 {
541 /* A better path was found for the node,
542 on the `first' list. */
543 step->code = runp;
544 step->last = current;
545
546 /* Update the cost for all steps. */
547 for (step = first; step != NULL;
548 step = step->next)
549 /* But don't update the start nodes. */
550 if (step->code != NULL)
551 {
552 struct derivation_step *back;
553 int hi, lo;
554
555 hi = step->code->cost_hi;
556 lo = step->code->cost_lo;
557
558 for (back = step->last; back->code != NULL;
559 back = back->last)
560 {
561 hi += back->code->cost_hi;
562 lo += back->code->cost_lo;
563 }
564
565 step->cost_hi = hi;
566 step->cost_lo = lo;
567 }
568
569 /* Likewise for the nodes on the solution list.
570 Also update best_cost accordingly. */
571 for (step = solution; step != NULL;
572 step = step->next)
573 {
574 step->cost_hi = (step->code->cost_hi
575 + step->last->cost_hi);
576 step->cost_lo = (step->code->cost_lo
577 + step->last->cost_lo);
578
579 if (step->cost_hi < best_cost_hi
580 || (step->cost_hi == best_cost_hi
581 && step->cost_lo < best_cost_lo))
582 {
583 best_cost_hi = step->cost_hi;
584 best_cost_lo = step->cost_lo;
585 }
586 }
587 }
588 }
589
590 runp = runp->same;
591 }
592 while (runp != NULL);
593
594 break;
595 }
596 else if (cmpres < 0)
597 node = node->left;
598 else
599 node = node->right;
600 }
601 }
602
603 if (solution != NULL)
604 {
605 /* We really found a way to do the transformation. */
606
607 /* Choose the best solution. This is easy because we know that
608 the solution list has at most length 2 (one for every possible
609 goal node). */
610 if (solution->next != NULL)
611 {
612 struct derivation_step *solution2 = solution->next;
613
614 if (solution2->cost_hi < solution->cost_hi
615 || (solution2->cost_hi == solution->cost_hi
616 && solution2->cost_lo < solution->cost_lo))
617 solution = solution2;
618 }
619
620 /* Now build a data structure describing the transformation steps. */
621 result = gen_steps (solution, toset_expand ?: toset,
622 fromset_expand ?: fromset, handle, nsteps);
623 }
624 else
625 {
626 /* We haven't found a transformation. Clear the result values. */
627 *handle = NULL;
628 *nsteps = 0;
629 }
630
631 /* Add result in any case to list of known derivations. */
632 add_derivation (fromset_expand ?: fromset, toset_expand ?: toset,
633 *handle, *nsteps);
634
635 return result;
636 }
637
638
639 /* Control of initialization. */
640 __libc_once_define (static, once);
641
642
643 static const char *
644 do_lookup_alias (const char *name)
645 {
646 struct gconv_alias key;
647 struct gconv_alias **found;
648
649 key.fromname = (char *) name;
650 found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
651 return found != NULL ? (*found)->toname : NULL;
652 }
653
654
655 int
656 internal_function
657 __gconv_compare_alias (const char *name1, const char *name2)
658 {
659 int result;
660
661 /* Ensure that the configuration data is read. */
662 __libc_once (once, __gconv_read_conf);
663
664 if (__gconv_compare_alias_cache (name1, name2, &result) != 0)
665 result = strcmp (do_lookup_alias (name1) ?: name1,
666 do_lookup_alias (name2) ?: name2);
667
668 return result;
669 }
670
671
672 int
673 internal_function
674 __gconv_find_transform (const char *toset, const char *fromset,
675 struct __gconv_step **handle, size_t *nsteps,
676 int flags)
677 {
678 const char *fromset_expand;
679 const char *toset_expand;
680 int result;
681
682 /* Ensure that the configuration data is read. */
683 __libc_once (once, __gconv_read_conf);
684
685 /* Acquire the lock. */
686 __libc_lock_lock (lock);
687
688 result = __gconv_lookup_cache (toset, fromset, handle, nsteps, flags);
689 if (result != __GCONV_NODB)
690 {
691 /* We have a cache and could resolve the request, successful or not. */
692 __libc_lock_unlock (lock);
693 return result;
694 }
695
696 /* If we don't have a module database return with an error. */
697 if (__gconv_modules_db == NULL)
698 {
699 __libc_lock_unlock (lock);
700 return __GCONV_NOCONV;
701 }
702
703 /* See whether the names are aliases. */
704 fromset_expand = do_lookup_alias (fromset);
705 toset_expand = do_lookup_alias (toset);
706
707 if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0)
708 /* We are not supposed to create a pseudo transformation (means
709 copying) when the input and output character set are the same. */
710 && (strcmp (toset, fromset) == 0
711 || (toset_expand != NULL && strcmp (toset_expand, fromset) == 0)
712 || (fromset_expand != NULL
713 && (strcmp (toset, fromset_expand) == 0
714 || (toset_expand != NULL
715 && strcmp (toset_expand, fromset_expand) == 0)))))
716 {
717 /* Both character sets are the same. */
718 __libc_lock_unlock (lock);
719 return __GCONV_NOCONV;
720 }
721
722 result = find_derivation (toset, toset_expand, fromset, fromset_expand,
723 handle, nsteps);
724
725 /* Release the lock. */
726 __libc_lock_unlock (lock);
727
728 /* The following code is necessary since `find_derivation' will return
729 GCONV_OK even when no derivation was found but the same request
730 was processed before. I.e., negative results will also be cached. */
731 return (result == __GCONV_OK
732 ? (*handle == NULL ? __GCONV_NOCONV : __GCONV_OK)
733 : result);
734 }
735
736
737 /* Release the entries of the modules list. */
738 int
739 internal_function
740 __gconv_close_transform (struct __gconv_step *steps, size_t nsteps)
741 {
742 int result = __GCONV_OK;
743 size_t cnt;
744
745 /* Acquire the lock. */
746 __libc_lock_lock (lock);
747
748 #ifndef STATIC_GCONV
749 cnt = nsteps;
750 while (cnt-- > 0)
751 __gconv_release_step (&steps[cnt]);
752 #endif
753
754 /* If we use the cache we free a bit more since we don't keep any
755 transformation records around, they are cheap enough to
756 recreate. */
757 __gconv_release_cache (steps, nsteps);
758
759 /* Release the lock. */
760 __libc_lock_unlock (lock);
761
762 return result;
763 }
764
765
766 /* Free the modules mentioned. */
767 static void
768 internal_function
769 free_modules_db (struct gconv_module *node)
770 {
771 if (node->left != NULL)
772 free_modules_db (node->left);
773 if (node->right != NULL)
774 free_modules_db (node->right);
775 do
776 {
777 struct gconv_module *act = node;
778 node = node->same;
779 if (act->module_name[0] == '/')
780 free (act);
781 }
782 while (node != NULL);
783 }
784
785
786 /* Free all resources if necessary. */
787 libc_freeres_fn (free_mem)
788 {
789 if (__gconv_alias_db != NULL)
790 __tdestroy (__gconv_alias_db, free);
791
792 if (__gconv_modules_db != NULL)
793 free_modules_db (__gconv_modules_db);
794
795 if (known_derivations != NULL)
796 __tdestroy (known_derivations, free_derivation);
797 }