pull-prototypes-to-head-20020821
[openafs.git] / src / libadmin / cfg / cfgclient.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /* This file implements configuration functions in the following categories:
11  *   cfg_Client*()       - perform minimally necessary client configuration.
12  */
13
14 #include <afsconfig.h>
15 #include <afs/param.h>
16
17 RCSID("$Header$");
18
19 #include <afs/stds.h>
20
21 #include <stddef.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <errno.h>
25
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #else
29 #ifdef HAVE_STRINGS_H
30 #include <strings.h>
31 #endif
32 #endif
33
34 #include <pthread.h>
35
36 #include <afs/afs_Admin.h>
37 #include <afs/afs_AdminErrors.h>
38 #include <afs/afs_utilAdmin.h>
39
40 #include <afs/dirpath.h>
41 #include <afs/cellconfig.h>
42
43 #ifdef AFS_NT40_ENV
44 #include <windows.h>
45 #include <WINNT/afsreg.h>
46 #include <WINNT/afssw.h>
47 #include <cellservdb.h>
48 #endif
49
50 #include "cfginternal.h"
51 #include "afs_cfgAdmin.h"
52
53
54 /* Local declarations and definitions */
55
56 #define CSDB_OP_ADD  0   /* add a client CellServDB entry */
57 #define CSDB_OP_REM  1   /* remove a client CellServDB entry */
58
59 static int
60 ClientCellServDbUpdate(int updateOp,
61                        void *hostHandle,
62                        const char *cellName,
63                        const char *dbentry,
64                        afs_status_p st);
65
66 static int
67 CacheManagerStart(unsigned timeout,
68                   afs_status_p st);
69
70 static int
71 CacheManagerStop(unsigned timeout,
72                  afs_status_p st);
73
74
75
76
77 /* ---------------- Exported AFS Client functions ------------------ */
78
79
80 /*
81  * cfg_ClientQueryStatus() -- Query status of static client configuration
82  *     on host, i.e., status of required configuration files, etc.
83  *     Upon successful completion *configStP is set to the client
84  *     configuration status, with a value of zero (0) indicating that
85  *     the configuration is valid.
86  *
87  *     If client configuration is not valid then *cellNameP is set to NULL;
88  *     otherwise, *cellNameP is an allocated buffer containing client cell.
89  *
90  *     If client software (cache-manager) is not installed then *versionP is
91  *     undefined; otherwise *versionP is 34 for 3.4, 35 for 3.5, etc.
92  *
93  *     Note: Client configuration is checked even if the client software
94  *           is not installed.  This is useful for tools that require
95  *           client configuration information but NOT the actual
96  *           client (cache-manager); for example, the AFS Server Manager.
97  */
98 int ADMINAPI
99 cfg_ClientQueryStatus(const char *hostName,    /* name of host */
100                       short *isInstalledP,     /* client software installed */
101                       unsigned *versionP,      /* client software version */
102                       afs_status_p configStP,  /* client config status */
103                       char **cellNameP,        /* client's cell */
104                       afs_status_p st)         /* completion status */
105 {
106     int rc = 1;
107     afs_status_t tst2, tst = 0;
108     afs_status_t clientSt = 0;
109     char *clientCellName = NULL;
110     short cmInstalled = 0;
111     unsigned cmVersion;
112
113     /* validate parameters */
114
115     if (hostName == NULL || *hostName == '\0') {
116         tst = ADMCFGHOSTNAMENULL;
117     } else if (strlen(hostName) > (MAXHOSTCHARS - 1)) {
118         tst = ADMCFGHOSTNAMETOOLONG;
119     } else if (isInstalledP == NULL) {
120         tst = ADMCFGINSTALLEDFLAGPNULL;
121     } else if (versionP == NULL) {
122         tst = ADMCFGVERSIONPNULL;
123     } else if (configStP == NULL) {
124         tst = ADMCFGCONFIGSTATUSPNULL;
125     } else if (cellNameP == NULL) {
126         tst = ADMCFGCELLNAMEPNULL;
127     }
128
129     /* remote configuration not yet supported; hostName must be local host */
130
131     if (tst == 0) {
132         short isLocal;
133
134         if (!cfgutil_HostNameIsLocal(hostName, &isLocal, &tst2)) {
135             tst = tst2;
136         } else if (!isLocal) {
137             tst = ADMCFGNOTSUPPORTED;
138         }
139     }
140
141     /* determine if client software (CM) is installed and if so what version */
142
143 #ifdef AFS_NT40_ENV
144     /* Windows - cache manager is a service */
145     if (tst == 0) {
146         DWORD svcState;
147
148         if (!cfgutil_WindowsServiceQuery(AFSREG_CLT_SVC_NAME,
149                                          &svcState,
150                                          &tst2)) {
151             /* CM not installed, or insufficient privilege to check */
152             if (tst2 == ADMNOPRIV) {
153                 tst = tst2;
154             } else {
155                 cmInstalled = 0;
156             }
157         } else {
158             /* CM installed, get version */
159             unsigned major, minor, patch;
160
161             cmInstalled = 1;
162
163             if (afssw_GetClientVersion(&major, &minor, &patch)) {
164                 /* failed to retrieve version information */
165                 if (errno == EACCES) {
166                     tst = ADMNOPRIV;
167                 } else {
168                     tst = ADMCFGCLIENTVERSIONNOTREAD;
169                 }
170             } else {
171                 cmVersion = (major * 10) + minor;
172             }
173         }
174     }
175 #else
176     if (tst == 0) {
177         /* function not yet implemented for Unix */
178         tst = ADMCFGNOTSUPPORTED;
179     }
180 #endif /* AFS_NT40_ENV */
181
182
183     /* check static client configuration; not necessary that client
184      * software (CM) be installed for this information to be valid and useable.
185      */
186
187     if (tst == 0) {
188         struct afsconf_dir *confdir;
189
190         if ((confdir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH)) == NULL) {
191             /* the client configuration appears to be missing/invalid */
192             clientSt = ADMCFGCLIENTBASICINFOINVALID;
193         } else {
194             struct afsconf_entry *cellentry;
195
196             if (confdir->cellName == NULL || *confdir->cellName == '\0') {
197                 /* no cell set for client */
198                 clientSt = ADMCFGCLIENTNOTINCELL;
199             } else {
200                 for (cellentry = confdir->entries;
201                      cellentry != NULL;
202                      cellentry = cellentry->next) {
203                     if (!strcasecmp(confdir->cellName,
204                                     cellentry->cellInfo.name)) {
205                         break;
206                     }
207                 }
208
209                 if (cellentry == NULL) {
210                     clientSt = ADMCFGCLIENTCELLNOTINDB;
211                 } else if (cellentry->cellInfo.numServers <= 0) {
212                     clientSt = ADMCFGCLIENTCELLHASNODBENTRIES;
213                 }
214             }
215
216             if (tst == 0 && clientSt == 0) {
217                 /* everything looks good; malloc cell name buffer to return */
218                 clientCellName =
219                     (char *) malloc(strlen(cellentry->cellInfo.name) + 1);
220                 if (clientCellName == NULL) {
221                     tst = ADMNOMEM;
222                 } else {
223                     strcpy(clientCellName, cellentry->cellInfo.name);
224                 }
225             }
226
227             (void)afsconf_Close(confdir);
228         }
229     }
230
231     /* return result of query */
232
233     if (tst == 0) {
234         /* return client status and cell name */
235         *isInstalledP = cmInstalled;
236         *versionP = cmVersion;
237         *configStP = clientSt;
238
239         if (clientSt == 0) {
240             *cellNameP = clientCellName;
241         } else {
242             *cellNameP = NULL;
243         }
244     } else {
245         /* indicate failure */
246         rc = 0;
247
248         /* free cell name if allocated before failure */
249         if (clientCellName != NULL) {
250             free(clientCellName);
251         }
252     }
253     if (st != NULL) {
254         *st = tst;
255     }
256     return rc;
257 }
258
259
260 /*
261  * cfg_ClientSetCell() -- Define default client cell for host.
262  *
263  *     The cellDbHosts argument is a multistring containing the names of
264  *     the existing database servers already configured in the cell; this
265  *     multistring list can be obtained via cfg_CellServDbEnumerate().
266  *     If configuring the first server in a new cell then the cellDbHosts
267  *     list contains only the name of that host.
268  *
269  *     Warning: client (cache-manager) should be stopped prior to setting cell.
270  */
271 int ADMINAPI
272 cfg_ClientSetCell(void *hostHandle,        /* host config handle */
273                   const char *cellName,    /* cell name */
274                   const char *cellDbHosts, /* cell database hosts */
275                   afs_status_p st)         /* completion status */
276 {
277     int rc = 1;
278     afs_status_t tst2, tst = 0;
279     cfg_host_p cfg_host = (cfg_host_p)hostHandle;
280
281     /* validate parameters */
282
283     if (!cfgutil_HostHandleValidate(cfg_host, &tst2)) {
284         tst = tst2;
285     } else if (cellName == NULL || *cellName == '\0') {
286         tst = ADMCFGCELLNAMENULL;
287     } else if (strlen(cellName) > (MAXCELLCHARS - 1)) {
288         tst = ADMCFGCELLNAMETOOLONG;
289     } else if (!cfgutil_HostHandleCellNameCompatible(cfg_host, cellName)) {
290         tst = ADMCFGCELLNAMECONFLICT;
291     } else if (cellDbHosts == NULL || *cellDbHosts == '\0') {
292         tst = ADMCFGCELLDBHOSTSNULL;
293     }
294
295     /* remote configuration not yet supported in this function */
296
297     if (tst == 0) {
298         if (!cfg_host->is_local) {
299             tst = ADMCFGNOTSUPPORTED;
300         }
301     }
302
303     /* define cell database hosts */
304
305 #ifdef AFS_NT40_ENV
306     if (tst == 0) {
307         CELLSERVDB clientDb;
308
309         if (!CSDB_ReadFile(&clientDb, AFSDIR_CLIENT_CELLSERVDB_FILEPATH)) {
310             tst = ADMCFGCLIENTCELLSERVDBNOTREAD;
311         } else {
312             CELLDBLINE *cellLinep = CSDB_FindCell(&clientDb, cellName);
313
314             if (cellLinep != NULL) {
315                 /* cell entry exists; remove host entries */
316                 if (!CSDB_RemoveCellServers(&clientDb, cellLinep)) {
317                     /* should never happen */
318                     tst = ADMCFGCLIENTCELLSERVDBEDITFAILED;
319                 }
320             } else {
321                 /* cell entry does not exist; add it */
322                 cellLinep = CSDB_AddCell(&clientDb, cellName, NULL, NULL);
323
324                 if (cellLinep == NULL) {
325                     tst = ADMNOMEM;
326                 }
327             }
328
329             if (tst == 0) {
330                 /* add new host entries to cell */
331                 const char *dbHost = cellDbHosts;
332                 int dbHostCount = 0;
333
334                 while (*dbHost != '\0' && tst == 0) {
335                     size_t dbHostLen = strlen(dbHost);
336                     const char *dbHostAddrStr;
337
338                     if (dbHostLen > (MAXHOSTCHARS - 1)) {
339                         tst = ADMCFGHOSTNAMETOOLONG;
340                     } else if (dbHostCount >= MAXHOSTSPERCELL) {
341                         tst = ADMCFGCELLDBHOSTCOUNTTOOLARGE;
342                     } else if (!cfgutil_HostNameGetAddressString(dbHost,
343                                                                  &dbHostAddrStr,
344                                                                  &tst2)) {
345                         tst = tst2;
346                     } else if (CSDB_AddCellServer(&clientDb,
347                                                   cellLinep,
348                                                   dbHostAddrStr,
349                                                   dbHost) == NULL) {
350                         tst = ADMNOMEM;
351                     } else {
352                         dbHostCount++;
353                         dbHost += dbHostLen + 1;
354                     }
355                 }
356
357                 if (tst == 0) {
358                     /* edit successful; write CellServDB */
359                     if (!CSDB_WriteFile(&clientDb)) {
360                         tst = ADMCFGCLIENTCELLSERVDBNOTWRITTEN;
361                     }
362                 }
363             }
364
365             CSDB_FreeFile(&clientDb);
366         }
367     }
368 #else
369     if (tst == 0) {
370         /* function not yet implemented for Unix */
371         tst = ADMCFGNOTSUPPORTED;
372     }
373 #endif /* AFS_NT40_ENV */
374
375
376     /* define default client cell */
377
378 #ifdef AFS_NT40_ENV
379     if (tst == 0) {
380         if (afssw_SetClientCellName(cellName)) {
381             /* failed to set cell name in registry (ThisCell equivalent) */
382             if (errno == EACCES) {
383                 tst = ADMNOPRIV;
384             } else {
385                 tst = ADMCFGCLIENTTHISCELLNOTWRITTEN;
386             }
387         }
388     }
389 #else
390     if (tst == 0) {
391         /* function not yet implemented for Unix */
392         tst = ADMCFGNOTSUPPORTED;
393     }
394 #endif /* AFS_NT40_ENV */
395
396
397     /* help any underlying packages adjust to cell change */
398
399     if (tst == 0) {
400         int rc;
401
402         if ((rc = ka_CellConfig(AFSDIR_CLIENT_ETC_DIRPATH)) != 0) {
403             tst = rc;
404         }
405     }
406
407     if (tst != 0) {
408         rc = 0;
409     }
410     if (st != NULL) {
411         *st = tst;
412     }
413     return rc;
414 }
415
416
417 /*
418  * cfg_ClientCellServDbAdd() -- Add entry to client CellServDB on host.
419  */
420 int ADMINAPI
421 cfg_ClientCellServDbAdd(void *hostHandle,        /* host config handle */
422                         const char *cellName,    /* cell name */
423                         const char *dbentry,     /* cell database entry */
424                         afs_status_p st)         /* completion status */
425 {
426     return ClientCellServDbUpdate(CSDB_OP_ADD,
427                                   hostHandle,
428                                   cellName,
429                                   dbentry,
430                                   st);
431 }
432
433
434 /*
435  * cfg_ClientCellServDbRemove() -- Remove entry from client CellServDB
436  *     on host.
437  */
438 int ADMINAPI
439 cfg_ClientCellServDbRemove(void *hostHandle,      /* host config handle */
440                            const char *cellName,  /* cell name */
441                            const char *dbentry,   /* cell database entry */
442                            afs_status_p st)       /* completion status */
443 {
444     return ClientCellServDbUpdate(CSDB_OP_REM,
445                                   hostHandle,
446                                   cellName,
447                                   dbentry,
448                                   st);
449 }
450
451
452 /*
453  * cfg_ClientStop() -- Stop the client (cache manager) on host.
454  *
455  *     Timeout is the maximum time (in seconds) to wait for client to stop.
456  */
457 int ADMINAPI
458 cfg_ClientStop(void *hostHandle,      /* host config handle */
459                unsigned int timeout,  /* timeout in seconds */
460                afs_status_p st)       /* completion status */
461 {
462     int rc = 1;
463     afs_status_t tst2, tst = 0;
464     cfg_host_p cfg_host = (cfg_host_p)hostHandle;
465
466     /* validate parameters */
467
468     if (!cfgutil_HostHandleValidate(cfg_host, &tst2)) {
469         tst = tst2;
470     }
471
472     /* remote configuration not yet supported in this function */
473
474     if (tst == 0) {
475         if (!cfg_host->is_local) {
476             tst = ADMCFGNOTSUPPORTED;
477         }
478     }
479
480     /* stop client */
481
482     if (tst == 0) {
483         if (!CacheManagerStop(timeout, &tst2)) {
484             tst = tst2;
485         }
486     }
487
488     if (tst != 0) {
489         /* indicate failure */
490         rc = 0;
491     }
492     if (st != NULL) {
493         *st = tst;
494     }
495     return rc;
496 }
497
498
499 /*
500  * cfg_ClientStart() -- Start the client (cache manager) on host.
501  *
502  *     Timeout is the maximum time (in seconds) to wait for client to start.
503  */
504 int ADMINAPI
505 cfg_ClientStart(void *hostHandle,      /* host config handle */
506                 unsigned int timeout,  /* timeout in seconds */
507                 afs_status_p st)       /* completion status */
508 {
509     int rc = 1;
510     afs_status_t tst2, tst = 0;
511     cfg_host_p cfg_host = (cfg_host_p)hostHandle;
512
513     /* validate parameters */
514
515     if (!cfgutil_HostHandleValidate(cfg_host, &tst2)) {
516         tst = tst2;
517     }
518
519     /* remote configuration not yet supported in this function */
520
521     if (tst == 0) {
522         if (!cfg_host->is_local) {
523             tst = ADMCFGNOTSUPPORTED;
524         }
525     }
526
527     /* start client */
528
529     if (tst == 0) {
530         if (!CacheManagerStart(timeout, &tst2)) {
531             tst = tst2;
532         }
533     }
534
535     if (tst != 0) {
536         /* indicate failure */
537         rc = 0;
538     }
539     if (st != NULL) {
540         *st = tst;
541     }
542     return rc;
543 }
544
545
546
547 /* ---------------- Local functions ------------------ */
548
549
550 /*
551  * ClientCellServDbUpdate() -- add or remove a client CellServDB entry.
552  *
553  *     Common function implementing cfg_ClientCellServDb{Add/Remove}().
554  */
555 static int
556 ClientCellServDbUpdate(int updateOp,
557                        void *hostHandle,
558                        const char *cellName,
559                        const char *dbentry,
560                        afs_status_p st)
561 {
562     int rc = 1;
563     afs_status_t tst2, tst = 0;
564     cfg_host_p cfg_host = (cfg_host_p)hostHandle;
565     char dbentryFull[MAXHOSTCHARS];
566
567     /* validate parameters and resolve dbentry to fully qualified name */
568
569     if (!cfgutil_HostHandleValidate(cfg_host, &tst2)) {
570         tst = tst2;
571     } else if (cellName == NULL || *cellName == '\0') {
572         tst = ADMCFGCELLNAMENULL;
573     } else if (strlen(cellName) > (MAXCELLCHARS - 1)) {
574         tst = ADMCFGCELLNAMETOOLONG;
575     } else if (dbentry == NULL || *dbentry == '\0') {
576         tst = ADMCFGHOSTNAMENULL;
577     } else if (strlen(dbentry) > (MAXHOSTCHARS - 1)) {
578         tst = ADMCFGHOSTNAMETOOLONG;
579     } else if (!cfgutil_HostNameGetFull(dbentry, dbentryFull, &tst2)) {
580         tst = tst2;
581     }
582
583     /* remote configuration not yet supported in this function */
584
585     if (tst == 0) {
586         if (!cfg_host->is_local) {
587             tst = ADMCFGNOTSUPPORTED;
588         }
589     }
590
591     /* modify local client CellServDB entry for specified cell */
592
593 #ifdef AFS_NT40_ENV
594     if (tst == 0) {
595         CELLSERVDB clientDb;
596
597         if (!CSDB_ReadFile(&clientDb, AFSDIR_CLIENT_CELLSERVDB_FILEPATH)) {
598             tst = ADMCFGCLIENTCELLSERVDBNOTREAD;
599         } else {
600             CELLDBLINE *cellLinep = CSDB_FindCell(&clientDb, cellName);
601             CELLDBLINE *serverLinep = NULL;
602             int serverLineCount = 0;
603
604             if (cellLinep != NULL) {
605                 /* found cellName, now find server to add/remove */
606                 CELLDBLINE *workingLinep;
607
608                 for (workingLinep = cellLinep->pNext;
609                      workingLinep != NULL;
610                      workingLinep = workingLinep->pNext) {
611                     CELLDBLINEINFO lineInfo;
612
613                     if (!CSDB_CrackLine(&lineInfo, workingLinep->szLine)) {
614                         /* not a server (or cell) line; perhaps a comment */
615                         continue;
616                     } else if (lineInfo.szCell[0] != '\0') {
617                         /* hit a new cell line */
618                         break;
619                     } else {
620                         /* found a server line; check if is host of interest */
621                         short isValid;
622                         int dbentryAddr = ntohl(lineInfo.ipServer);
623
624                         serverLineCount++;
625
626                         if (!cfgutil_HostAddressIsValid(dbentryFull,
627                                                         dbentryAddr,
628                                                         &isValid,
629                                                         &tst2)) {
630                             tst = tst2;
631                             break;
632                         } else if (isValid) {
633                             /* found server of interest */
634                             serverLinep = workingLinep;
635                             break;
636                         }
637                     }
638                 }
639             }
640
641             if (tst == 0) {
642                 if (updateOp == CSDB_OP_ADD && serverLinep == NULL) {
643                     if (cellLinep == NULL) {
644                         cellLinep = CSDB_AddCell(&clientDb,
645                                                  cellName, NULL, NULL);
646                     }
647
648                     if (cellLinep == NULL) {
649                         tst = ADMNOMEM;
650                     } else if (serverLineCount >= MAXHOSTSPERCELL) {
651                         tst = ADMCFGCLIENTCELLSERVDBNOSPACE;
652                     } else {
653                         const char *dbentryAddrStr;
654
655                         if (!cfgutil_HostNameGetAddressString(dbentryFull,
656                                                               &dbentryAddrStr,
657                                                               &tst2)) {
658                             tst = tst2;
659                         } else {
660                             serverLinep = CSDB_AddCellServer(&clientDb,
661                                                              cellLinep,
662                                                              dbentryAddrStr,
663                                                              dbentryFull);
664                             if (serverLinep == NULL) {
665                                 tst = ADMNOMEM;
666                             }
667                         }
668                     }
669                 } else if (updateOp == CSDB_OP_REM && serverLinep != NULL) {
670                     (void)CSDB_RemoveLine(&clientDb, serverLinep);
671                 }
672
673                 if (tst == 0) {
674                     if (!CSDB_WriteFile(&clientDb)) {
675                         tst = ADMCFGCLIENTCELLSERVDBNOTWRITTEN;
676                     }
677                 }
678             }
679
680             CSDB_FreeFile(&clientDb);
681         }
682     }
683 #else
684     if (tst == 0) {
685         /* function not yet implemented for Unix */
686         tst = ADMCFGNOTSUPPORTED;
687     }
688 #endif /* AFS_NT40_ENV */
689
690     if (tst != 0) {
691         /* indicate failure */
692         rc = 0;
693     }
694     if (st != NULL) {
695         *st = tst;
696     }
697     return rc;
698 }
699
700
701 /*
702  * CacheManagerStart() -- Start the local AFS cache manager.
703  *
704  *     Timeout is the maximum time (in seconds) to wait for the CM to start.
705  *
706  * RETURN CODES: 1 success, 0 failure (st indicates why)
707  */
708 static int
709 CacheManagerStart(unsigned timeout,
710                   afs_status_p st)
711 {
712     int rc = 1;
713     afs_status_t tst2, tst = 0;
714
715 #ifdef AFS_NT40_ENV
716     /* Windows - cache manager is a service */
717     short wasRunning;
718
719     if (!cfgutil_WindowsServiceStart(AFSREG_CLT_SVC_NAME,
720                                      0, NULL,
721                                      timeout,
722                                      &wasRunning,
723                                      &tst2)) {
724         tst = tst2;
725     }
726 #else
727     /* function not yet implemented for Unix */
728     tst = ADMCFGNOTSUPPORTED;
729 #endif /* AFS_NT40_ENV */
730
731     if (tst != 0) {
732         /* indicate failure */
733         rc = 0;
734     }
735     if (st != NULL) {
736         *st = tst;
737     }
738     return rc;
739 }
740
741
742 /*
743  * CacheManagerStop() -- Stop the local AFS cache manager.
744  *
745  *     Timeout is the maximum time (in seconds) to wait for the CM to stop.
746  *
747  * RETURN CODES: 1 success, 0 failure (st indicates why)
748  */
749 static int
750 CacheManagerStop(unsigned timeout,
751                  afs_status_p st)
752 {
753     int rc = 1;
754     afs_status_t tst2, tst = 0;
755
756 #ifdef AFS_NT40_ENV
757     /* Windows - cache manager is a service */
758     short wasStopped;
759
760     if (!cfgutil_WindowsServiceStop(AFSREG_CLT_SVC_NAME,
761                                     timeout,
762                                     &wasStopped,
763                                     &tst2)) {
764         tst = tst2;
765     }
766 #else
767     /* function not yet implemented for Unix */
768     tst = ADMCFGNOTSUPPORTED;
769 #endif /* AFS_NT40_ENV */
770
771     if (tst != 0) {
772         /* indicate failure */
773         rc = 0;
774     }
775     if (st != NULL) {
776         *st = tst;
777     }
778     return rc;
779 }