1 /*
2 * Motif
3 *
4 * Copyright (c) 1987-2012, The Open Group. All rights reserved.
5 *
6 * These libraries and programs are free software; you can
7 * redistribute them and/or modify them under the terms of the GNU
8 * Lesser General Public License as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * These libraries and programs are distributed in the hope that
13 * they will be useful, but WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU Lesser General Public License for more
16 * details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these librararies and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
22 */
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27
28 #ifdef REV_INFO
29 #ifndef lint
30 static char rcsid[] = "$XConsortium: Mrmhier.c /main/17 1996/11/13 14:01:19 drk $"
31 #endif
32 #endif
33
34
35 /*
36 *++
37 * FACILITY:
38 *
39 * UIL Resource Manager (URM):
40 *
41 * ABSTRACT:
42 *
43 *--
44 */
45
46 /*
47 * This file contains routines which assist in managing URM hierarchies
48 */
49
50
51 /*
52 *
53 * INCLUDE FILES
54 *
55 */
56
57 #include <stdio.h>
58 #include <Mrm/MrmAppl.h>
59 #include <Mrm/Mrm.h>
60
61 #include <Xm/XmosI.h> /* for _XmOSInitPath() */
62 #include <Xm/XmI.h> /* for _XmGetDefaultDisplay() */
63
64 #include "MrmMsgI.h"
65
66 #ifndef NO_MESSAGE_CATALOG
67 #if !defined(NL_CAT_LOCALE)
68 #define NL_CAT_LOCALE 0
69 #endif
70 #endif
71
72
73 /*
74 *
75 * TABLE OF CONTENTS
76 *
77 * Urm__OpenHierarchy Open hierarchy (internal version)
78 *
79 * Urm__CloseHierarchy Close hierarchy (internal version)
80 *
81 * UrmHGetIndexedResource Get resource from hierarchy
82 *
83 */
84
85
86 /*
87 *
88 * DEFINE and MACRO DEFINITIONS
89 *
90 */
91
92 static Cardinal I18NOpenFile (Display *display,
93 String name ,
94 MrmOsOpenParamPtr os_ext ,
95 IDBFile *file_id_return );
96
97
98 /*
99 *
100 * OWN VARIABLE DECLARATIONS
101 *
102 */
103 static char *uidPath;
104 static SubstitutionRec uidSubs[1];
105
106
107
108 /*
109 *++
110 *
111 * PROCEDURE DESCRIPTION:
112 *
113 * This routine allocates a hierarchy descriptor, and opens
114 * all the IDB files in the hierarchy. It initializes the
115 * optimized search lists in the hierarchy from the open files.
116 * All files are closed if there are any errors.
117 *
118 * FORMAL PARAMETERS:
119 *
120 * num_files The number of files in the name list
121 * name_list A list of the file names
122 * os_ext_list A list of system-dependent ancillary
123 * structures corresponding to the files.
124 * This parameter may be NULL.
125 * hierarchy_id_return To return the hierarchy id
126 *
127 * IMPLICIT INPUTS:
128 *
129 * IMPLICIT OUTPUTS:
130 *
131 * FUNCTION VALUE:
132 *
133 * MrmSUCCESS operation succeeded
134 * MrmFAILURE allocation or other failure
135 * MrmDISPLAY_NOT_OPENED Display hasn't been opened yet
136 * Others see UrmIdbOpenFileRead
137 *
138 * SIDE EFFECTS:
139 *
140 *--
141 */
142
143 Cardinal
Urm__OpenHierarchy(MrmCount num_files,String * name_list,MrmOsOpenParamPtr * os_ext_list,MrmHierarchy * hierarchy_id_return,MrmFlag in_memory,unsigned char * uid_buffer)144 Urm__OpenHierarchy (MrmCount num_files,
145 String *name_list,
146 MrmOsOpenParamPtr *os_ext_list,
147 MrmHierarchy *hierarchy_id_return,
148 MrmFlag in_memory,
149 unsigned char *uid_buffer)
150 {
151
152 /*
153 * Local variables
154 */
155 Cardinal result ; /* function result */
156 MrmHierarchy hiptr ; /* hierarchy descriptor */
157 MrmCount list_size ; /* # bytes for file lists */
158 int ndx ; /* loop index */
159 IDBFile *idvec ; /* current file id vector */
160 int file_ndx ; /* file loop index */
161 IDBFile cur_file ; /* current IDB file */
162 URMResourceContextPtr class_ctx; /* for class compression table */
163 URMResourceContextPtr resource_ctx; /* for resource compression table */
164 Display *display; /* display for XtResolvePathNames */
165 char err_stg[300];
166
167 #ifndef NO_MESSAGE_CATALOG
168 static Boolean first = True;
169
170 /*
171 * We open the message catalog from MrmOpenHierarchy... routines.
172 * We cannot open it from MrmInitialize, since this call can be done
173 * before Xt has been initialized, so at that time the locale could
174 * be wrong.
175 */
176
177 /* We only want to open the message catalog once. */
178
179 if (first)
180 {
181 Mrm_catd = catopen("Mrm", NL_CAT_LOCALE);
182 first = False;
183 }
184 #endif
185
186 if ( os_ext_list == NULL )
187 {
188 display = NULL;
189 }
190 else
191 {
192 MrmOsOpenParamPtr os_data;
193 os_data = *os_ext_list;
194 if (os_data->display == NULL)
195 {
196 display = NULL;
197 }
198 else
199 {
200 display = os_data->display;
201 }
202 }
203
204 if (display == NULL)
205 {
206 display = _XmGetDefaultDisplay();
207 }
208
209 if (display == NULL)
210 {
211 return Urm__UT_Error ("Urm__OpenHierarchy", _MrmMMsg_0030,
212 NULL, NULL, MrmDISPLAY_NOT_OPENED);
213 };
214
215 /*
216 * If the uidPath was previously set, XtFree it so we can try any
217 * new paths that may have been setup.
218 */
219 if (uidPath != 0)
220 {
221 XtFree (uidPath);
222 uidPath = 0;
223 }
224
225 /*
226 * Allocate a hierarchy, and allocate all file lists.
227 */
228 hiptr = (MrmHierarchy) XtMalloc (sizeof(MrmHierarchyDesc)) ;
229 if ( hiptr == NULL ) return MrmFAILURE ;
230
231 hiptr->validation = MrmHIERARCHY_VALID;
232 hiptr->num_file = 0 ;
233
234 list_size = num_files * sizeof(IDBFile) ;
235 hiptr->file_list = (IDBFile *) XtMalloc (list_size) ;
236 if ( hiptr->file_list == NULL ) return MrmFAILURE ;
237
238 for ( ndx=URMgMin ; ndx<=URMgMax ; ndx++ )
239 {
240 hiptr->grp_num[ndx] = 0 ;
241 idvec = (IDBFile *) XtMalloc (list_size) ;
242 if ( idvec == NULL ) return MrmFAILURE ;
243 hiptr->grp_ids[ndx] = idvec ;
244 }
245
246 hiptr->name_registry = NULL;
247
248 /*
249 * Now open each file. Any failure causes an error return, with any
250 * open files closed and the descriptor and lists freed.
251 */
252 for ( file_ndx=0 ; file_ndx<num_files ; file_ndx++ )
253 {
254 if ( in_memory == TRUE )
255 {
256 result = UrmIdbOpenBuffer(uid_buffer, &cur_file) ;
257 switch ( result )
258 {
259 case MrmSUCCESS:
260 break;
261 case MrmNOT_VALID:
262 sprintf (err_stg, _MrmMMsg_0113);
263 break;
264 default:
265 sprintf (err_stg, _MrmMMsg_0114);
266 break;
267 }
268 }
269 else if ( os_ext_list == NULL )
270 result = I18NOpenFile (display, name_list[file_ndx], NULL, &cur_file) ;
271 else
272 result = I18NOpenFile (display, name_list[file_ndx],
273 os_ext_list[file_ndx], &cur_file) ;
274 if ( result != MrmSUCCESS )
275 {
276 XtFree (uidPath);
277 uidPath = 0;
278 Urm__CloseHierarchy (hiptr) ;
279 return result;
280 }
281
282 hiptr->file_list[hiptr->num_file] = cur_file ;
283 hiptr->num_file++ ;
284 for ( ndx=URMgMin ; ndx<=URMgMax ; ndx++ )
285 if ( cur_file->group_counts[ndx] > 0 )
286 {
287 idvec = hiptr->grp_ids[ndx] ;
288 idvec[hiptr->grp_num[ndx]] = cur_file ;
289 hiptr->grp_num[ndx]++ ;
290 }
291
292 /*
293 * Attempt to read in compression tables for this UID file.
294 * Retain and fixup the tables if they are found.
295 */
296 cur_file->class_ctable = NULL;
297 cur_file->resource_ctable = NULL;
298 result = UrmGetResourceContext ((char *(*)())NULL, (void(*)())NULL,
299 0, &class_ctx);
300 if ( result != MrmSUCCESS ) return result;
301 result = UrmGetResourceContext ((char *(*)())NULL, (void(*)())NULL,
302 0, &resource_ctx);
303 if ( result != MrmSUCCESS ) return result;
304 result = UrmGetIndexedLiteral (cur_file, UilMrmClassTableIndex,
305 class_ctx);
306 if ( result != MrmSUCCESS ) continue;
307 result = UrmGetIndexedLiteral (cur_file, UilMrmResourceTableIndex,
308 resource_ctx);
309 if ( result != MrmSUCCESS ) continue;
310
311 /*
312 * Retain the buffers from the contexts, but free the contexts
313 * themselves. Fixup the tables.
314 */
315 cur_file->class_ctable = (UidCompressionTablePtr)
316 UrmRCBuffer (class_ctx);
317 UrmRCSetBuffer (class_ctx, NULL);
318 UrmFreeResourceContext (class_ctx);
319 Urm__FixupCompressionTable (cur_file->class_ctable, TRUE,
320 cur_file->byte_swapped);
321 cur_file->resource_ctable = (UidCompressionTablePtr)
322 UrmRCBuffer (resource_ctx);
323 UrmRCSetBuffer (resource_ctx, NULL);
324 UrmFreeResourceContext (resource_ctx);
325 Urm__FixupCompressionTable (cur_file->resource_ctable, FALSE,
326 cur_file->byte_swapped);
327 }
328
329 /*
330 * successfully opened. Free the uidPath at this point
331 */
332 XtFree (uidPath);
333 uidPath = 0;
334 *hierarchy_id_return = hiptr ;
335 return MrmSUCCESS ;
336
337 }
338
339
340
341 /*
342 *++
343 *
344 * PROCEDURE DESCRIPTION:
345 *
346 * This is the internal routine which closes a URM search hierarchy
347 *
348 * FORMAL PARAMETERS:
349 *
350 * hierarchy_id ID of an open URM database hierarchy
351 *
352 * IMPLICIT INPUTS:
353 *
354 * IMPLICIT OUTPUTS:
355 *
356 * FUNCTION VALUE:
357 *
358 * MrmSUCCESS operation succeeded
359 * MrmBAD_HIERARCHY invalid URM hierarchy
360 * MrmFAILURE operation failed, no further reason
361 *
362 * SIDE EFFECTS:
363 *
364 *--
365 */
366
367 Cardinal
Urm__CloseHierarchy(MrmHierarchy hierarchy_id)368 Urm__CloseHierarchy (MrmHierarchy hierarchy_id)
369 {
370
371 /*
372 * Local variables
373 */
374 int ndx ; /* loop index */
375 URMHashTableEntryPtr cp, np; /* for fixing 7303 */
376
377 /*
378 * validity check the hierarchy, then close all files, deallocate all
379 * lists, and deallocate the descriptor
380 */
381 if ( hierarchy_id == NULL )
382 return Urm__UT_Error ("Urm__CloseHierarchy", _MrmMMsg_0023,
383 NULL, NULL, MrmBAD_HIERARCHY) ;
384 if ( ! MrmHierarchyValid(hierarchy_id) )
385 return Urm__UT_Error ("Urm__CloseHierarchy", _MrmMMsg_0024,
386 NULL, NULL, MrmBAD_HIERARCHY) ;
387
388 for ( ndx=0 ; ndx<hierarchy_id->num_file ; ndx++ )
389 if (hierarchy_id->file_list[ndx]->in_memory == FALSE )
390 UrmIdbCloseFile (hierarchy_id->file_list[ndx], FALSE) ;
391
392 /* Begin fixing DTS 7303 */
393 if(hierarchy_id->name_registry){
394 for ( ndx=0 ; ndx<k_hash_table_size; ndx++ )
395 if((cp = hierarchy_id->name_registry[ndx]) != NULL)
396 while(cp){
397 np = cp->az_next_entry;
398 XtFree((char*)cp);
399 cp = np;
400 };
401 XtFree ((char*)hierarchy_id->name_registry);
402 }
403 /* End fixing DTS 7303 */
404
405 XtFree ((char*)hierarchy_id->file_list);
406
407 for ( ndx=URMgMin ; ndx<=URMgMax ; ndx++ )
408 XtFree ((char*)hierarchy_id->grp_ids[ndx]) ;
409
410 hierarchy_id->validation = 0;
411 XtFree ((char*)hierarchy_id) ;
412 return MrmSUCCESS ;
413
414 }
415
416
417
418 /*
419 *++
420 *
421 * PROCEDURE DESCRIPTION:
422 *
423 * UrmHGetIndexedResource attempts to retrieve a resource
424 * from an open URM hierarchy. It functions exactly like
425 * UrmIdbGetIndexedResource except that it queries each file
426 * in the hierarchy in turn. It uses the optimized search lists
427 * where possible.
428 *
429 * FORMAL PARAMETERS:
430 *
431 * hierarchy_id open URM hierarchy to search
432 * index case-sensitive index for the entry to match
433 * group_filter if not null, entry found must match this group
434 * type_filter if not null, entry found must match this type
435 * context_id URM resource context to receieve data block
436 * file_id_return to return file in which resource was found
437 *
438 * IMPLICIT INPUTS:
439 *
440 * IMPLICIT OUTPUTS:
441 *
442 * FUNCTION VALUE:
443 *
444 * MrmSUCCESS operation succeeded
445 * MrmBAD_HIERARCHY invalid URM hierarchy
446 * MrmNOT_FOUND entry not found
447 * MrmWRONG_GROUP entry didn't match group filter
448 * MrmWRONG_TYPE entry didn't match type filter
449 * MrmFAILURE operation failed, no further reason
450 *
451 * SIDE EFFECTS:
452 *
453 *--
454 */
455
456 Cardinal
UrmHGetIndexedResource(MrmHierarchy hierarchy_id,String index,MrmGroup group_filter,MrmType type_filter,URMResourceContextPtr context_id,IDBFile * file_id_return)457 UrmHGetIndexedResource (MrmHierarchy hierarchy_id,
458 String index ,
459 MrmGroup group_filter,
460 MrmType type_filter,
461 URMResourceContextPtr context_id,
462 IDBFile *file_id_return)
463 {
464
465 /*
466 * Local variables
467 */
468 Cardinal result ; /* function results */
469 IDBFile *file_ids ; /* list of files to search */
470 int num_ids ; /* number of entries in file_ids */
471 int ndx ; /* loop index */
472
473
474 /*
475 * Validate hierarchy. Then loop through the files which define the resource,
476 * searching for the desired resource.
477 */
478 if ( hierarchy_id == NULL )
479 return Urm__UT_Error ("UrmHGetIndexedResource", _MrmMMsg_0023,
480 NULL, NULL, MrmBAD_HIERARCHY) ;
481 if ( ! MrmHierarchyValid(hierarchy_id) )
482 return Urm__UT_Error ("UrmHGetIndexedResource", _MrmMMsg_0024,
483 NULL, context_id, MrmBAD_HIERARCHY) ;
484
485 if ( group_filter>=URMgMin && group_filter<=URMgMax)
486 {
487 file_ids = hierarchy_id->grp_ids[group_filter] ;
488 num_ids = hierarchy_id->grp_num[group_filter] ;
489 }
490 else
491 {
492 file_ids = hierarchy_id->file_list ;
493 num_ids = hierarchy_id->num_file ;
494 }
495 for ( ndx=0 ; ndx<num_ids ; ndx++ )
496 {
497 *file_id_return = file_ids[ndx] ;
498 result = UrmIdbGetIndexedResource
499 (*file_id_return, index, group_filter, type_filter, context_id) ;
500 if ( result == MrmSUCCESS ) return result ;
501 }
502
503 /*
504 * Not found
505 */
506 return MrmNOT_FOUND ;
507
508 }
509
510
511
512 /*
513 *++
514 *
515 * PROCEDURE DESCRIPTION:
516 *
517 * This routine inserts the names and their associated values into
518 * the hierarchy's name registration hash table (which is constructed
519 * if needed). In all respects it functions like Urm__WCI_RegisterNames.
520 *
521 * FORMAL PARAMETERS:
522 *
523 * hierarchy_id open URM hierarchy in which to register names.
524 * names A vector of case-sensitive callback routine names.
525 * values A vector of the corresponding routine addresses
526 * num_cb The number of entries in names and values.
527 *
528 * IMPLICIT INPUTS:
529 *
530 * IMPLICIT OUTPUTS:
531 *
532 * FUNCTION VALUE:
533 *
534 * MrmSUCCESS operation succeeded
535 *
536 * SIDE EFFECTS:
537 *
538 *--
539 */
540
541 Cardinal
Urm__RegisterNamesInHierarchy(MrmHierarchy hierarchy_id,String * names,XtPointer * values,MrmCount num_cb)542 Urm__RegisterNamesInHierarchy (MrmHierarchy hierarchy_id,
543 String *names,
544 XtPointer *values,
545 MrmCount num_cb)
546 {
547
548 URMHashTableEntryPtr *name_table;
549 Boolean inited = FALSE;
550 int ndx;
551 URMHashTableEntryPtr hash_entry;
552 char *current_name;
553 char *current_value;
554
555
556 /*
557 * Make sure the hash table is initialized
558 */
559 name_table = hierarchy_id->name_registry;
560 if ( name_table == NULL )
561 {
562 name_table = (URMHashTableEntryPtr *)
563 XtMalloc(sizeof(URMHashTableEntryPtr)*k_hash_table_size);
564 /* Begin fixing DTS 7303 */
565 for ( ndx=0 ; ndx<k_hash_table_size; ndx++ )
566 name_table[ndx] = NULL;
567 /* End fixing DTS 7303 */
568 hierarchy_id->name_registry = name_table;
569 hash_initialize (name_table, &inited);
570 }
571
572 /*
573 * Store each name-value pair in the hash table.
574 */
575 for (ndx = 0 ; ndx < num_cb ; ndx++)
576 {
577 current_name = names [ndx];
578 current_value = values [ndx];
579 hash_entry = (URMHashTableEntryPtr)
580 hash_insert_name (name_table, current_name);
581 hash_entry->az_value = current_value;
582 }
583
584 return MrmSUCCESS;
585
586 }
587
588
589
590 /*
591 *++
592 *
593 * PROCEDURE DESCRIPTION:
594 *
595 * This routine returns the value registered for a name. It first
596 * attempts to look up the name in the hierarchy's name registry.
597 * If that fails, or their is no registry, then a global lookup is
598 * attempted.
599 *
600 * FORMAL PARAMETERS:
601 *
602 * hierarchy_id open URM hierarchy to search.
603 * name case-sensitive name to be matched
604 * value_return to return value.
605 *
606 * IMPLICIT INPUTS:
607 *
608 * IMPLICIT OUTPUTS:
609 *
610 * FUNCTION VALUE:
611 *
612 * MrmSUCCESS operation succeeded
613 * MrmNOT_FOUND no match found
614 *
615 * SIDE EFFECTS:
616 *
617 *--
618 */
619
620 Cardinal
Urm__LookupNameInHierarchy(MrmHierarchy hierarchy_id,String name,XtPointer * value_return)621 Urm__LookupNameInHierarchy (MrmHierarchy hierarchy_id,
622 String name,
623 XtPointer *value_return)
624
625 {
626
627 URMHashTableEntryPtr *name_table;
628 URMHashTableEntryPtr hash_entry;
629
630
631 /*
632 * Look up in hierarchy first (if there is a registry)
633 */
634 name_table = hierarchy_id->name_registry;
635 if ( name_table != NULL )
636 {
637 hash_entry = (URMHashTableEntryPtr) hash_find_name (name_table, name);
638 if (hash_entry != NULL)
639 {
640 *value_return = hash_entry->az_value;
641 return MrmSUCCESS;
642 }
643 }
644
645 /*
646 * Fall back on global table
647 */
648 return Urm__WCI_LookupRegisteredName (name, value_return);
649
650 }
651
652
653
654 /*
655 *++
656 *
657 * PROCEDURE DESCRIPTION:
658 *
659 * This routine opens a single UID file in a platform-dependent way,
660 * performing i18n language switching in order to do so.
661 *
662 * Per the latest agreement on semantics, this routine does:
663 * - first, try to open in the local directory (that is, with
664 * no switching).
665 * - second, try language switching and open
666 *
667 * FORMAL PARAMETERS:
668 *
669 * name A system-dependent string specifying the IDB file
670 * to be opened.
671 * os_ext An operating-system specific structure which
672 * supports using specific file system features
673 * file_id_return returns the IDB file id used in all other IDB routines
674 *
675 * IMPLICIT INPUTS:
676 *
677 * IMPLICIT OUTPUTS:
678 *
679 * FUNCTION VALUE:
680 *
681 * SIDE EFFECTS:
682 *
683 *--
684 */
685
686 static Cardinal
I18NOpenFile(Display * display,String name,MrmOsOpenParamPtr os_ext,IDBFile * file_id_return)687 I18NOpenFile (Display *display,
688 String name,
689 MrmOsOpenParamPtr os_ext,
690 IDBFile *file_id_return)
691 {
692
693 /*
694 * Local variables
695 */
696 Cardinal result; /* function results */
697 char dummy[300]; /* file name (unused) */
698 char err_stg[300];
699
700 /*
701 * Use XtResolvePathName. If the last 4 characters of the file name
702 * are not .uid then pass in the suffix of .uid. If a file is not
703 * found with the suffix passed in then try without the suffix.
704 */
705 char *resolvedname; /* current resolved name */
706 Boolean user_path ;
707
708 uidSubs[0].substitution = name;
709
710 if (uidPath == 0)
711 {
712 uidPath = _XmOSInitPath(name, "UIDPATH", &user_path);
713 if (user_path) uidSubs[0].match = 'U';
714 else uidSubs[0].match = MATCH_CHAR ;
715 }
716
717 resolvedname = 0;
718
719 /*
720 * Check and see if the .uid suffix is already on the file. If not then try to
721 * resolve the pathname with .uid suffix first. If that fails or the suffix is
722 * already on the file then just try to resolve the pathname.
723 */
724 if ( strcmp (&name[strlen(name)-4],".uid") != 0 )
725 resolvedname = XtResolvePathname (display,
726 "uid",
727 NULL,
728 ".uid",
729 uidPath,
730 uidSubs,
731 XtNumber(uidSubs),
732 (XtFilePredicate)NULL);
733
734 /*
735 * No .uid suffix or a failure to resolve the pathname with the .uid suffix
736 */
737 if (resolvedname == 0)
738 resolvedname = XtResolvePathname (display,
739 "uid",
740 NULL,
741 NULL,
742 uidPath,
743 uidSubs,
744 XtNumber(uidSubs),
745 (XtFilePredicate)NULL);
746
747 if (resolvedname == 0)
748 {
749 sprintf (err_stg, _MrmMMsg_0031, name) ;
750 return Urm__UT_Error ("I18NOpenFile", err_stg, NULL, NULL, MrmNOT_FOUND);
751 }
752
753 result = UrmIdbOpenFileRead (resolvedname, os_ext, file_id_return, dummy) ;
754 switch ( result )
755 {
756 case MrmSUCCESS:
757 break;
758 case MrmNOT_VALID:
759 sprintf (err_stg, _MrmMMsg_0032, resolvedname) ;
760 break;
761 case MrmNOT_FOUND:
762 default:
763 sprintf (err_stg, _MrmMMsg_0031, resolvedname) ;
764 break;
765 }
766
767 XtFree(resolvedname); /* allocated in XtResolvePathName() */
768
769 if (result == MrmSUCCESS)
770 return result;
771 else
772 return Urm__UT_Error ("I18NOpenFile", err_stg, NULL, NULL, result);
773 }
774
775