xref: /netbsd-src/external/bsd/am-utils/dist/amd/autil.c (revision 8bae5d409deb915cf7c8f0539fae22ff2cb8a313)
1*8bae5d40Schristos /*	$NetBSD: autil.c,v 1.1.1.3 2015/01/17 16:34:15 christos Exp $	*/
2a53f50b9Schristos 
3a53f50b9Schristos /*
4*8bae5d40Schristos  * Copyright (c) 1997-2014 Erez Zadok
5a53f50b9Schristos  * Copyright (c) 1990 Jan-Simon Pendry
6a53f50b9Schristos  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
7a53f50b9Schristos  * Copyright (c) 1990 The Regents of the University of California.
8a53f50b9Schristos  * All rights reserved.
9a53f50b9Schristos  *
10a53f50b9Schristos  * This code is derived from software contributed to Berkeley by
11a53f50b9Schristos  * Jan-Simon Pendry at Imperial College, London.
12a53f50b9Schristos  *
13a53f50b9Schristos  * Redistribution and use in source and binary forms, with or without
14a53f50b9Schristos  * modification, are permitted provided that the following conditions
15a53f50b9Schristos  * are met:
16a53f50b9Schristos  * 1. Redistributions of source code must retain the above copyright
17a53f50b9Schristos  *    notice, this list of conditions and the following disclaimer.
18a53f50b9Schristos  * 2. Redistributions in binary form must reproduce the above copyright
19a53f50b9Schristos  *    notice, this list of conditions and the following disclaimer in the
20a53f50b9Schristos  *    documentation and/or other materials provided with the distribution.
21*8bae5d40Schristos  * 3. Neither the name of the University nor the names of its contributors
22a53f50b9Schristos  *    may be used to endorse or promote products derived from this software
23a53f50b9Schristos  *    without specific prior written permission.
24a53f50b9Schristos  *
25a53f50b9Schristos  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26a53f50b9Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27a53f50b9Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28a53f50b9Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29a53f50b9Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30a53f50b9Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31a53f50b9Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32a53f50b9Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33a53f50b9Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34a53f50b9Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35a53f50b9Schristos  * SUCH DAMAGE.
36a53f50b9Schristos  *
37a53f50b9Schristos  *
38a53f50b9Schristos  * File: am-utils/amd/autil.c
39a53f50b9Schristos  *
40a53f50b9Schristos  */
41a53f50b9Schristos 
42a53f50b9Schristos /*
43a53f50b9Schristos  * utilities specified to amd, taken out of the older amd/util.c.
44a53f50b9Schristos  */
45a53f50b9Schristos 
46a53f50b9Schristos #ifdef HAVE_CONFIG_H
47a53f50b9Schristos # include <config.h>
48a53f50b9Schristos #endif /* HAVE_CONFIG_H */
49a53f50b9Schristos #include <am_defs.h>
50a53f50b9Schristos #include <amd.h>
51a53f50b9Schristos 
52a53f50b9Schristos int NumChildren = 0;		/* number of children of primary amd */
53a53f50b9Schristos static char invalid_keys[] = "\"'!;@ \t\n";
54a53f50b9Schristos 
55a53f50b9Schristos /****************************************************************************
56a53f50b9Schristos  *** MACROS                                                               ***
57a53f50b9Schristos  ****************************************************************************/
58a53f50b9Schristos 
59a53f50b9Schristos #ifdef HAVE_TRANSPORT_TYPE_TLI
60a53f50b9Schristos # define PARENT_USLEEP_TIME	100000 /* 0.1 seconds */
61a53f50b9Schristos #endif /* HAVE_TRANSPORT_TYPE_TLI */
62a53f50b9Schristos 
63a53f50b9Schristos 
64a53f50b9Schristos /****************************************************************************
65a53f50b9Schristos  *** FORWARD DEFINITIONS                                                  ***
66a53f50b9Schristos  ****************************************************************************/
67a53f50b9Schristos static void domain_strip(char *otherdom, char *localdom);
68a53f50b9Schristos static int dofork(void);
69a53f50b9Schristos 
70a53f50b9Schristos 
71a53f50b9Schristos /****************************************************************************
72a53f50b9Schristos  *** FUNCTIONS                                                             ***
73a53f50b9Schristos  ****************************************************************************/
74a53f50b9Schristos 
75a53f50b9Schristos /*
76a53f50b9Schristos  * Copy s into p, reallocating p if necessary
77a53f50b9Schristos  */
78a53f50b9Schristos char *
strealloc(char * p,char * s)79a53f50b9Schristos strealloc(char *p, char *s)
80a53f50b9Schristos {
81a53f50b9Schristos   size_t len = strlen(s) + 1;
82a53f50b9Schristos 
83a53f50b9Schristos   p = (char *) xrealloc((voidp) p, len);
84a53f50b9Schristos 
85a53f50b9Schristos   xstrlcpy(p, s, len);
86a53f50b9Schristos #ifdef DEBUG_MEM
87a53f50b9Schristos # if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
88a53f50b9Schristos   malloc_verify();
89a53f50b9Schristos # endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
90a53f50b9Schristos #endif /* DEBUG_MEM */
91a53f50b9Schristos   return p;
92a53f50b9Schristos }
93a53f50b9Schristos 
94a53f50b9Schristos 
95a53f50b9Schristos /*
96a53f50b9Schristos  * Strip off the trailing part of a domain
97a53f50b9Schristos  * to produce a short-form domain relative
98a53f50b9Schristos  * to the local host domain.
99a53f50b9Schristos  * Note that this has no effect if the domain
100a53f50b9Schristos  * names do not have the same number of
101a53f50b9Schristos  * components.  If that restriction proves
102a53f50b9Schristos  * to be a problem then the loop needs recoding
103a53f50b9Schristos  * to skip from right to left and do partial
104a53f50b9Schristos  * matches along the way -- ie more expensive.
105a53f50b9Schristos  */
106a53f50b9Schristos static void
domain_strip(char * otherdom,char * localdom)107a53f50b9Schristos domain_strip(char *otherdom, char *localdom)
108a53f50b9Schristos {
109a53f50b9Schristos   char *p1, *p2;
110a53f50b9Schristos 
111a53f50b9Schristos   if ((p1 = strchr(otherdom, '.')) &&
112a53f50b9Schristos       (p2 = strchr(localdom, '.')) &&
113a53f50b9Schristos       STREQ(p1 + 1, p2 + 1))
114a53f50b9Schristos     *p1 = '\0';
115a53f50b9Schristos }
116a53f50b9Schristos 
117a53f50b9Schristos 
118a53f50b9Schristos /*
119a53f50b9Schristos  * Normalize a host name: replace cnames with real names, and decide if to
120a53f50b9Schristos  * strip domain name or not.
121a53f50b9Schristos  */
122a53f50b9Schristos void
host_normalize(char ** chp)123a53f50b9Schristos host_normalize(char **chp)
124a53f50b9Schristos {
125a53f50b9Schristos   /*
126a53f50b9Schristos    * Normalize hosts is used to resolve host name aliases
127a53f50b9Schristos    * and replace them with the standard-form name.
128a53f50b9Schristos    * Invoked with "-n" command line option.
129a53f50b9Schristos    */
130a53f50b9Schristos   if (gopt.flags & CFM_NORMALIZE_HOSTNAMES) {
131a53f50b9Schristos     struct hostent *hp;
132a53f50b9Schristos     hp = gethostbyname(*chp);
133a53f50b9Schristos     if (hp && hp->h_addrtype == AF_INET) {
134a53f50b9Schristos       dlog("Hostname %s normalized to %s", *chp, hp->h_name);
135a53f50b9Schristos       *chp = strealloc(*chp, (char *) hp->h_name);
136a53f50b9Schristos     }
137a53f50b9Schristos   }
138a53f50b9Schristos   if (gopt.flags & CFM_DOMAIN_STRIP) {
139a53f50b9Schristos     domain_strip(*chp, hostd);
140a53f50b9Schristos   }
141a53f50b9Schristos }
142a53f50b9Schristos 
143a53f50b9Schristos 
144a53f50b9Schristos /*
145a53f50b9Schristos  * Keys are not allowed to contain " ' ! or ; to avoid
146a53f50b9Schristos  * problems with macro expansions.
147a53f50b9Schristos  */
148a53f50b9Schristos int
valid_key(char * key)149a53f50b9Schristos valid_key(char *key)
150a53f50b9Schristos {
151a53f50b9Schristos   while (*key)
152a53f50b9Schristos     if (strchr(invalid_keys, *key++))
153a53f50b9Schristos       return FALSE;
154a53f50b9Schristos   return TRUE;
155a53f50b9Schristos }
156a53f50b9Schristos 
157a53f50b9Schristos 
158a53f50b9Schristos void
forcibly_timeout_mp(am_node * mp)159a53f50b9Schristos forcibly_timeout_mp(am_node *mp)
160a53f50b9Schristos {
161*8bae5d40Schristos   mntfs *mf = mp->am_al->al_mnt;
162a53f50b9Schristos   /*
163a53f50b9Schristos    * Arrange to timeout this node
164a53f50b9Schristos    */
165a53f50b9Schristos   if (mf && ((mp->am_flags & AMF_ROOT) ||
166a53f50b9Schristos 	     (mf->mf_flags & (MFF_MOUNTING | MFF_UNMOUNTING)))) {
1674bcd344eSchristos     /*
1684bcd344eSchristos      * We aren't going to schedule a timeout, so we need to notify the
1694bcd344eSchristos      * child here unless we are already unmounting, in which case that
1704bcd344eSchristos      * process is responsible for notifying the child.
1714bcd344eSchristos      */
172a53f50b9Schristos     if (mf->mf_flags & MFF_UNMOUNTING)
173a53f50b9Schristos       plog(XLOG_WARNING, "node %s is currently being unmounted, ignoring timeout request", mp->am_path);
1744bcd344eSchristos     else {
175a53f50b9Schristos       plog(XLOG_WARNING, "ignoring timeout request for active node %s", mp->am_path);
1764bcd344eSchristos       notify_child(mp, AMQ_UMNT_FAILED, EBUSY, 0);
1774bcd344eSchristos     }
178a53f50b9Schristos   } else {
179a53f50b9Schristos     plog(XLOG_INFO, "\"%s\" forcibly timed out", mp->am_path);
180a53f50b9Schristos     mp->am_flags &= ~AMF_NOTIMEOUT;
181a53f50b9Schristos     mp->am_ttl = clocktime(NULL);
182a53f50b9Schristos     /*
183a53f50b9Schristos      * Force mtime update of parent dir, to prevent DNLC/dcache from caching
184a53f50b9Schristos      * the old entry, which could result in ESTALE errors, bad symlinks, and
185a53f50b9Schristos      * more.
186a53f50b9Schristos      */
187a53f50b9Schristos     clocktime(&mp->am_parent->am_fattr.na_mtime);
188a53f50b9Schristos     reschedule_timeout_mp();
189a53f50b9Schristos   }
190a53f50b9Schristos }
191a53f50b9Schristos 
192a53f50b9Schristos 
193a53f50b9Schristos void
mf_mounted(mntfs * mf,bool_t call_free_opts)194a53f50b9Schristos mf_mounted(mntfs *mf, bool_t call_free_opts)
195a53f50b9Schristos {
196a53f50b9Schristos   int quoted;
197a53f50b9Schristos   int wasmounted = mf->mf_flags & MFF_MOUNTED;
198a53f50b9Schristos 
199a53f50b9Schristos   if (!wasmounted) {
200a53f50b9Schristos     /*
201a53f50b9Schristos      * If this is a freshly mounted
202a53f50b9Schristos      * filesystem then update the
203a53f50b9Schristos      * mntfs structure...
204a53f50b9Schristos      */
205a53f50b9Schristos     mf->mf_flags |= MFF_MOUNTED;
206a53f50b9Schristos     mf->mf_error = 0;
207a53f50b9Schristos 
208a53f50b9Schristos     /*
209a53f50b9Schristos      * Do mounted callback
210a53f50b9Schristos      */
211a53f50b9Schristos     if (mf->mf_ops->mounted)
212a53f50b9Schristos       mf->mf_ops->mounted(mf);
213a53f50b9Schristos 
214a53f50b9Schristos     /*
215*8bae5d40Schristos      * We used to free the mf_mo (options) here, however they're now stored
216*8bae5d40Schristos      * and managed with the mntfs and do not need to be free'd here (this ensures
217*8bae5d40Schristos      * that we use the same options to monitor/unmount the system as we used
218*8bae5d40Schristos      * to mount it).
219a53f50b9Schristos      */
220a53f50b9Schristos   }
221a53f50b9Schristos 
222a53f50b9Schristos   if (mf->mf_flags & MFF_RESTART) {
223a53f50b9Schristos     mf->mf_flags &= ~MFF_RESTART;
224a53f50b9Schristos     dlog("Restarted filesystem %s, flags 0x%x", mf->mf_mount, mf->mf_flags);
225a53f50b9Schristos   }
226a53f50b9Schristos 
227a53f50b9Schristos   /*
228a53f50b9Schristos    * Log message
229a53f50b9Schristos    */
230a53f50b9Schristos   quoted = strchr(mf->mf_info, ' ') != 0;
231a53f50b9Schristos   plog(XLOG_INFO, "%s%s%s %s fstype %s on %s",
232a53f50b9Schristos        quoted ? "\"" : "",
233a53f50b9Schristos        mf->mf_info,
234a53f50b9Schristos        quoted ? "\"" : "",
235a53f50b9Schristos        wasmounted ? "referenced" : "mounted",
236a53f50b9Schristos        mf->mf_ops->fs_type, mf->mf_mount);
237a53f50b9Schristos }
238a53f50b9Schristos 
239a53f50b9Schristos 
240a53f50b9Schristos void
am_mounted(am_node * mp)241a53f50b9Schristos am_mounted(am_node *mp)
242a53f50b9Schristos {
243a53f50b9Schristos   int notimeout = 0;		/* assume normal timeouts initially */
244*8bae5d40Schristos   mntfs *mf = mp->am_al->al_mnt;
245a53f50b9Schristos 
246a53f50b9Schristos   /*
247a53f50b9Schristos    * This is the parent mntfs which does the mf->mf_fo (am_opts type), and
248a53f50b9Schristos    * we're passing TRUE here to tell mf_mounted to actually free the
249a53f50b9Schristos    * am_opts.  See a related comment in mf_mounted().
250a53f50b9Schristos    */
251a53f50b9Schristos   mf_mounted(mf, TRUE);
252a53f50b9Schristos 
253a53f50b9Schristos #ifdef HAVE_FS_AUTOFS
254a53f50b9Schristos   if (mf->mf_flags & MFF_IS_AUTOFS)
255a53f50b9Schristos     autofs_mounted(mp);
256a53f50b9Schristos #endif /* HAVE_FS_AUTOFS */
257a53f50b9Schristos 
258a53f50b9Schristos   /*
259a53f50b9Schristos    * Patch up path for direct mounts
260a53f50b9Schristos    */
261*8bae5d40Schristos   if (mp->am_parent && mp->am_parent->am_al->al_mnt->mf_fsflags & FS_DIRECT)
262a53f50b9Schristos     mp->am_path = str3cat(mp->am_path, mp->am_parent->am_path, "/", ".");
263a53f50b9Schristos 
264a53f50b9Schristos   /*
265a53f50b9Schristos    * Check whether this mount should be cached permanently or not,
266a53f50b9Schristos    * and handle user-requested timeouts.
267a53f50b9Schristos    */
268a53f50b9Schristos   /* first check if file system was set to never timeout */
269a53f50b9Schristos   if (mf->mf_fsflags & FS_NOTIMEOUT)
270a53f50b9Schristos     notimeout = 1;
271a53f50b9Schristos   /* next, alter that decision by map flags */
272*8bae5d40Schristos 
273a53f50b9Schristos   if (mf->mf_mopts) {
274a53f50b9Schristos     mntent_t mnt;
275a53f50b9Schristos     mnt.mnt_opts = mf->mf_mopts;
276a53f50b9Schristos 
277a53f50b9Schristos     /* umount option: user wants to unmount this entry */
278a53f50b9Schristos     if (amu_hasmntopt(&mnt, "unmount") || amu_hasmntopt(&mnt, "umount"))
279a53f50b9Schristos       notimeout = 0;
280a53f50b9Schristos     /* noumount option: user does NOT want to unmount this entry */
281a53f50b9Schristos     if (amu_hasmntopt(&mnt, "nounmount") || amu_hasmntopt(&mnt, "noumount"))
282a53f50b9Schristos       notimeout = 1;
283a53f50b9Schristos     /* utimeout=N option: user wants to unmount this option AND set timeout */
284a53f50b9Schristos     if ((mp->am_timeo = hasmntval(&mnt, "utimeout")) == 0)
285a53f50b9Schristos       mp->am_timeo = gopt.am_timeo; /* otherwise use default timeout */
286a53f50b9Schristos     else
287a53f50b9Schristos       notimeout = 0;
288a53f50b9Schristos     /* special case: don't try to unmount "/" (it can never succeed) */
289a53f50b9Schristos     if (mf->mf_mount[0] == '/' && mf->mf_mount[1] == '\0')
290a53f50b9Schristos       notimeout = 1;
291a53f50b9Schristos   }
292a53f50b9Schristos   /* finally set actual flags */
293a53f50b9Schristos   if (notimeout) {
294a53f50b9Schristos     mp->am_flags |= AMF_NOTIMEOUT;
295a53f50b9Schristos     plog(XLOG_INFO, "%s set to never timeout", mp->am_path);
296a53f50b9Schristos   } else {
297a53f50b9Schristos     mp->am_flags &= ~AMF_NOTIMEOUT;
298a53f50b9Schristos     plog(XLOG_INFO, "%s set to timeout in %d seconds", mp->am_path, mp->am_timeo);
299a53f50b9Schristos   }
300a53f50b9Schristos 
301a53f50b9Schristos   /*
302a53f50b9Schristos    * If this node is a symlink then
303a53f50b9Schristos    * compute the length of the returned string.
304a53f50b9Schristos    */
305a53f50b9Schristos   if (mp->am_fattr.na_type == NFLNK)
306a53f50b9Schristos     mp->am_fattr.na_size = strlen(mp->am_link ? mp->am_link : mf->mf_mount);
307a53f50b9Schristos 
308a53f50b9Schristos   /*
309a53f50b9Schristos    * Record mount time, and update am_stats at the same time.
310a53f50b9Schristos    */
311a53f50b9Schristos   mp->am_stats.s_mtime = clocktime(&mp->am_fattr.na_mtime);
312a53f50b9Schristos   new_ttl(mp);
313a53f50b9Schristos 
314a53f50b9Schristos   /*
315a53f50b9Schristos    * Update mtime of parent node (copying "struct nfstime" in '=' below)
316a53f50b9Schristos    */
317*8bae5d40Schristos   if (mp->am_parent && mp->am_parent->am_al->al_mnt)
318a53f50b9Schristos     mp->am_parent->am_fattr.na_mtime = mp->am_fattr.na_mtime;
319a53f50b9Schristos 
320a53f50b9Schristos   /*
321a53f50b9Schristos    * This is ugly, but essentially unavoidable
322a53f50b9Schristos    * Sublinks must be treated separately as type==link
323a53f50b9Schristos    * when the base type is different.
324a53f50b9Schristos    */
325a53f50b9Schristos   if (mp->am_link && mf->mf_ops != &amfs_link_ops)
326a53f50b9Schristos     amfs_link_ops.mount_fs(mp, mf);
327a53f50b9Schristos 
328a53f50b9Schristos   /*
329a53f50b9Schristos    * Now, if we can, do a reply to our client here
330a53f50b9Schristos    * to speed things up.
331a53f50b9Schristos    */
332a53f50b9Schristos #ifdef HAVE_FS_AUTOFS
333a53f50b9Schristos   if (mp->am_flags & AMF_AUTOFS)
334a53f50b9Schristos     autofs_mount_succeeded(mp);
335a53f50b9Schristos   else
336a53f50b9Schristos #endif /* HAVE_FS_AUTOFS */
337a53f50b9Schristos     nfs_quick_reply(mp, 0);
338a53f50b9Schristos 
339a53f50b9Schristos   /*
340a53f50b9Schristos    * Update stats
341a53f50b9Schristos    */
342a53f50b9Schristos   amd_stats.d_mok++;
343a53f50b9Schristos }
344a53f50b9Schristos 
345a53f50b9Schristos 
346a53f50b9Schristos /*
347a53f50b9Schristos  * Replace mount point with a reference to an error filesystem.
348a53f50b9Schristos  * The mount point (struct mntfs) is NOT discarded,
349a53f50b9Schristos  * the caller must do it if it wants to _before_ calling this function.
350a53f50b9Schristos  */
351a53f50b9Schristos void
assign_error_mntfs(am_node * mp)352a53f50b9Schristos assign_error_mntfs(am_node *mp)
353a53f50b9Schristos {
354a53f50b9Schristos   int error;
355a53f50b9Schristos   dlog("assign_error_mntfs");
356*8bae5d40Schristos 
357*8bae5d40Schristos   if (mp->am_al == NULL) {
358*8bae5d40Schristos     plog(XLOG_ERROR, "%s: Can't assign error", __func__);
359*8bae5d40Schristos     return;
360*8bae5d40Schristos   }
361a53f50b9Schristos   /*
362a53f50b9Schristos    * Save the old error code
363a53f50b9Schristos    */
364a53f50b9Schristos   error = mp->am_error;
365a53f50b9Schristos   if (error <= 0)
366*8bae5d40Schristos     error = mp->am_al->al_mnt->mf_error;
367a53f50b9Schristos   /*
368a53f50b9Schristos    * Allocate a new error reference
369a53f50b9Schristos    */
370*8bae5d40Schristos   free_loc(mp->am_al);
371*8bae5d40Schristos   mp->am_al = new_loc();
372a53f50b9Schristos   /*
373a53f50b9Schristos    * Put back the error code
374a53f50b9Schristos    */
375*8bae5d40Schristos   mp->am_al->al_mnt->mf_error = error;
376*8bae5d40Schristos   mp->am_al->al_mnt->mf_flags |= MFF_ERROR;
377a53f50b9Schristos   /*
378a53f50b9Schristos    * Zero the error in the mount point
379a53f50b9Schristos    */
380a53f50b9Schristos   mp->am_error = 0;
381a53f50b9Schristos }
382a53f50b9Schristos 
383a53f50b9Schristos 
384a53f50b9Schristos /*
385a53f50b9Schristos  * Build a new map cache for this node, or re-use
386a53f50b9Schristos  * an existing cache for the same map.
387a53f50b9Schristos  */
388a53f50b9Schristos void
amfs_mkcacheref(mntfs * mf)389a53f50b9Schristos amfs_mkcacheref(mntfs *mf)
390a53f50b9Schristos {
391a53f50b9Schristos   char *cache;
392a53f50b9Schristos 
393a53f50b9Schristos   if (mf->mf_fo && mf->mf_fo->opt_cache)
394a53f50b9Schristos     cache = mf->mf_fo->opt_cache;
395a53f50b9Schristos   else
396a53f50b9Schristos     cache = "none";
397a53f50b9Schristos   mf->mf_private = (opaque_t) mapc_find(mf->mf_info,
398a53f50b9Schristos 					cache,
399a53f50b9Schristos 					(mf->mf_fo ? mf->mf_fo->opt_maptype : NULL),
400a53f50b9Schristos 					mf->mf_mount);
401a53f50b9Schristos   mf->mf_prfree = mapc_free;
402a53f50b9Schristos }
403a53f50b9Schristos 
404a53f50b9Schristos 
405a53f50b9Schristos /*
406a53f50b9Schristos  * Locate next node in sibling list which is mounted
407a53f50b9Schristos  * and is not an error node.
408a53f50b9Schristos  */
409a53f50b9Schristos am_node *
next_nonerror_node(am_node * xp)410a53f50b9Schristos next_nonerror_node(am_node *xp)
411a53f50b9Schristos {
412a53f50b9Schristos   mntfs *mf;
413a53f50b9Schristos 
414a53f50b9Schristos   /*
415a53f50b9Schristos    * Bug report (7/12/89) from Rein Tollevik <rein@ifi.uio.no>
416a53f50b9Schristos    * Fixes a race condition when mounting direct automounts.
417a53f50b9Schristos    * Also fixes a problem when doing a readdir on a directory
418a53f50b9Schristos    * containing hung automounts.
419a53f50b9Schristos    */
420a53f50b9Schristos   while (xp &&
421*8bae5d40Schristos 	 (!(mf = xp->am_al->al_mnt) ||	/* No mounted filesystem */
422a53f50b9Schristos 	  mf->mf_error != 0 ||	/* There was a mntfs error */
423a53f50b9Schristos 	  xp->am_error != 0 ||	/* There was a mount error */
424a53f50b9Schristos 	  !(mf->mf_flags & MFF_MOUNTED) ||	/* The fs is not mounted */
425a53f50b9Schristos 	  (mf->mf_server->fs_flags & FSF_DOWN))	/* The fs may be down */
426a53f50b9Schristos 	 )
427a53f50b9Schristos     xp = xp->am_osib;
428a53f50b9Schristos 
429a53f50b9Schristos   return xp;
430a53f50b9Schristos }
431a53f50b9Schristos 
432a53f50b9Schristos 
433a53f50b9Schristos /*
434a53f50b9Schristos  * Mount an automounter directory.
435a53f50b9Schristos  * The automounter is connected into the system
436a53f50b9Schristos  * as a user-level NFS server.  amfs_mount constructs
437a53f50b9Schristos  * the necessary NFS parameters to be given to the
438a53f50b9Schristos  * kernel so that it will talk back to us.
439a53f50b9Schristos  *
440a53f50b9Schristos  * NOTE: automounter mounts in themselves are using NFS Version 2 (UDP).
441a53f50b9Schristos  *
442a53f50b9Schristos  * NEW: on certain systems, mounting can be done using the
443a53f50b9Schristos  * kernel-level automount (autofs) support. In that case,
444a53f50b9Schristos  * we don't need NFS at all here.
445a53f50b9Schristos  */
446a53f50b9Schristos int
amfs_mount(am_node * mp,mntfs * mf,char * opts)447a53f50b9Schristos amfs_mount(am_node *mp, mntfs *mf, char *opts)
448a53f50b9Schristos {
449a53f50b9Schristos   char fs_hostname[MAXHOSTNAMELEN + MAXPATHLEN + 1];
450a53f50b9Schristos   int retry, error = 0, genflags;
451a53f50b9Schristos   int on_autofs = mf->mf_flags & MFF_ON_AUTOFS;
452a53f50b9Schristos   char *dir = mf->mf_mount;
453a53f50b9Schristos   mntent_t mnt;
454a53f50b9Schristos   MTYPE_TYPE type;
455a53f50b9Schristos   int forced_unmount = 0;	/* are we using forced unmounts? */
456*8bae5d40Schristos   u_long nfs_version = get_nfs_dispatcher_version(nfs_dispatcher);
457a53f50b9Schristos 
458*8bae5d40Schristos   memset(&mnt, 0, sizeof(mnt));
459a53f50b9Schristos   mnt.mnt_dir = dir;
460a53f50b9Schristos   mnt.mnt_fsname = pid_fsname;
461a53f50b9Schristos   mnt.mnt_opts = opts;
462a53f50b9Schristos 
463a53f50b9Schristos #ifdef HAVE_FS_AUTOFS
464a53f50b9Schristos   if (mf->mf_flags & MFF_IS_AUTOFS) {
465a53f50b9Schristos     type = MOUNT_TYPE_AUTOFS;
466a53f50b9Schristos     /*
467a53f50b9Schristos      * Make sure that amd's top-level autofs mounts are hidden by default
468a53f50b9Schristos      * from df.
469a53f50b9Schristos      * XXX: It works ok on Linux, might not work on other systems.
470a53f50b9Schristos      */
471a53f50b9Schristos     mnt.mnt_type = "autofs";
472a53f50b9Schristos   } else
473a53f50b9Schristos #endif /* HAVE_FS_AUTOFS */
474a53f50b9Schristos   {
475a53f50b9Schristos     type = MOUNT_TYPE_NFS;
476a53f50b9Schristos     /*
477a53f50b9Schristos      * Make sure that amd's top-level NFS mounts are hidden by default
478a53f50b9Schristos      * from df.
479a53f50b9Schristos      * If they don't appear to support the either the "ignore" mnttab
480a53f50b9Schristos      * option entry, or the "auto" one, set the mount type to "nfs".
481a53f50b9Schristos      */
482a53f50b9Schristos     mnt.mnt_type = HIDE_MOUNT_TYPE;
483a53f50b9Schristos   }
484a53f50b9Schristos 
485a53f50b9Schristos   retry = hasmntval(&mnt, MNTTAB_OPT_RETRY);
486a53f50b9Schristos   if (retry <= 0)
487a53f50b9Schristos     retry = 2;			/* XXX: default to 2 retries */
488a53f50b9Schristos 
489a53f50b9Schristos   /*
490a53f50b9Schristos    * SET MOUNT ARGS
491a53f50b9Schristos    */
492a53f50b9Schristos 
493a53f50b9Schristos   /*
494a53f50b9Schristos    * Make a ``hostname'' string for the kernel
495a53f50b9Schristos    */
496a53f50b9Schristos   xsnprintf(fs_hostname, sizeof(fs_hostname), "pid%ld@%s:%s",
497a53f50b9Schristos 	    get_server_pid(), am_get_hostname(), dir);
498a53f50b9Schristos   /*
499a53f50b9Schristos    * Most kernels have a name length restriction (64 bytes)...
500a53f50b9Schristos    */
501a53f50b9Schristos   if (strlen(fs_hostname) >= MAXHOSTNAMELEN)
502a53f50b9Schristos     xstrlcpy(fs_hostname + MAXHOSTNAMELEN - 3, "..",
503a53f50b9Schristos 	     sizeof(fs_hostname) - MAXHOSTNAMELEN + 3);
504a53f50b9Schristos #ifdef HOSTNAMESZ
505a53f50b9Schristos   /*
506a53f50b9Schristos    * ... and some of these restrictions are 32 bytes (HOSTNAMESZ)
507a53f50b9Schristos    * If you need to get the definition for HOSTNAMESZ found, you may
508a53f50b9Schristos    * add the proper header file to the conf/nfs_prot/nfs_prot_*.h file.
509a53f50b9Schristos    */
510a53f50b9Schristos   if (strlen(fs_hostname) >= HOSTNAMESZ)
511a53f50b9Schristos     xstrlcpy(fs_hostname + HOSTNAMESZ - 3, "..",
512a53f50b9Schristos 	     sizeof(fs_hostname) - HOSTNAMESZ + 3);
513a53f50b9Schristos #endif /* HOSTNAMESZ */
514a53f50b9Schristos 
515a53f50b9Schristos   /*
516a53f50b9Schristos    * Finally we can compute the mount genflags set above,
517a53f50b9Schristos    * and add any automounter specific flags.
518a53f50b9Schristos    */
519a53f50b9Schristos   genflags = compute_mount_flags(&mnt);
520a53f50b9Schristos #ifdef HAVE_FS_AUTOFS
521a53f50b9Schristos   if (on_autofs)
522a53f50b9Schristos     genflags |= autofs_compute_mount_flags(&mnt);
523a53f50b9Schristos #endif /* HAVE_FS_AUTOFS */
524a53f50b9Schristos   genflags |= compute_automounter_mount_flags(&mnt);
525a53f50b9Schristos 
526a53f50b9Schristos again:
527a53f50b9Schristos   if (!(mf->mf_flags & MFF_IS_AUTOFS)) {
528a53f50b9Schristos     nfs_args_t nfs_args;
529*8bae5d40Schristos     am_nfs_handle_t *fhp, anh;
530a53f50b9Schristos #ifndef HAVE_TRANSPORT_TYPE_TLI
531a53f50b9Schristos     u_short port;
532a53f50b9Schristos     struct sockaddr_in sin;
533a53f50b9Schristos #endif /* not HAVE_TRANSPORT_TYPE_TLI */
534a53f50b9Schristos 
535a53f50b9Schristos     /*
536a53f50b9Schristos      * get fhandle of remote path for automount point
537a53f50b9Schristos      */
538*8bae5d40Schristos     fhp = get_root_nfs_fh(dir, &anh);
539a53f50b9Schristos     if (!fhp) {
540a53f50b9Schristos       plog(XLOG_FATAL, "Can't find root file handle for %s", dir);
541a53f50b9Schristos       return EINVAL;
542a53f50b9Schristos     }
543a53f50b9Schristos 
544a53f50b9Schristos #ifndef HAVE_TRANSPORT_TYPE_TLI
545a53f50b9Schristos     /*
546a53f50b9Schristos      * Create sockaddr to point to the local machine.
547a53f50b9Schristos      */
548*8bae5d40Schristos     memset(&sin, 0, sizeof(sin));
549a53f50b9Schristos     /* as per POSIX, sin_len need not be set (used internally by kernel) */
550a53f50b9Schristos     sin.sin_family = AF_INET;
551a53f50b9Schristos     sin.sin_addr = myipaddr;
552a53f50b9Schristos     port = hasmntval(&mnt, MNTTAB_OPT_PORT);
553a53f50b9Schristos     if (port) {
554a53f50b9Schristos       sin.sin_port = htons(port);
555a53f50b9Schristos     } else {
556a53f50b9Schristos       plog(XLOG_ERROR, "no port number specified for %s", dir);
557a53f50b9Schristos       return EINVAL;
558a53f50b9Schristos     }
559a53f50b9Schristos #endif /* not HAVE_TRANSPORT_TYPE_TLI */
560a53f50b9Schristos 
561a53f50b9Schristos     /* setup the many fields and flags within nfs_args */
562a53f50b9Schristos #ifdef HAVE_TRANSPORT_TYPE_TLI
563a53f50b9Schristos     compute_nfs_args(&nfs_args,
564a53f50b9Schristos 		     &mnt,
565a53f50b9Schristos 		     genflags,
566a53f50b9Schristos 		     nfsncp,
567a53f50b9Schristos 		     NULL,	/* remote host IP addr is set below */
568*8bae5d40Schristos 		     nfs_version,
569a53f50b9Schristos 		     "udp",
570*8bae5d40Schristos 		     fhp,
571a53f50b9Schristos 		     fs_hostname,
572a53f50b9Schristos 		     pid_fsname);
573a53f50b9Schristos     /*
574a53f50b9Schristos      * IMPORTANT: set the correct IP address AFTERWARDS.  It cannot
575a53f50b9Schristos      * be done using the normal mechanism of compute_nfs_args(), because
576a53f50b9Schristos      * that one will allocate a new address and use NFS_SA_DREF() to copy
577a53f50b9Schristos      * parts to it, while assuming that the ip_addr passed is always
578a53f50b9Schristos      * a "struct sockaddr_in".  That assumption is incorrect on TLI systems,
579a53f50b9Schristos      * because they define a special macro HOST_SELF which is DIFFERENT
580a53f50b9Schristos      * than localhost (127.0.0.1)!
581a53f50b9Schristos      */
582a53f50b9Schristos     nfs_args.addr = &nfsxprt->xp_ltaddr;
583a53f50b9Schristos #else /* not HAVE_TRANSPORT_TYPE_TLI */
584a53f50b9Schristos     compute_nfs_args(&nfs_args,
585a53f50b9Schristos 		     &mnt,
586a53f50b9Schristos 		     genflags,
587a53f50b9Schristos 		     NULL,
588a53f50b9Schristos 		     &sin,
589*8bae5d40Schristos 		     nfs_version,
590a53f50b9Schristos 		     "udp",
591*8bae5d40Schristos 		     fhp,
592a53f50b9Schristos 		     fs_hostname,
593a53f50b9Schristos 		     pid_fsname);
594a53f50b9Schristos #endif /* not HAVE_TRANSPORT_TYPE_TLI */
595a53f50b9Schristos 
596a53f50b9Schristos     /*************************************************************************
597a53f50b9Schristos      * NOTE: while compute_nfs_args() works ok for regular NFS mounts	     *
598a53f50b9Schristos      * the toplvl one is not quite regular, and so some options must be      *
599a53f50b9Schristos      * corrected by hand more carefully, *after* compute_nfs_args() runs.    *
600a53f50b9Schristos      *************************************************************************/
601a53f50b9Schristos     compute_automounter_nfs_args(&nfs_args, &mnt);
602a53f50b9Schristos 
603a53f50b9Schristos     if (amuDebug(D_TRACE)) {
604a53f50b9Schristos       print_nfs_args(&nfs_args, 0);
605a53f50b9Schristos       plog(XLOG_DEBUG, "Generic mount flags 0x%x", genflags);
606a53f50b9Schristos     }
607a53f50b9Schristos 
608a53f50b9Schristos     /* This is it!  Here we try to mount amd on its mount points */
609a53f50b9Schristos     error = mount_fs(&mnt, genflags, (caddr_t) &nfs_args,
610a53f50b9Schristos 		     retry, type, 0, NULL, mnttab_file_name, on_autofs);
611a53f50b9Schristos 
612a53f50b9Schristos #ifdef HAVE_TRANSPORT_TYPE_TLI
613a53f50b9Schristos     free_knetconfig(nfs_args.knconf);
614a53f50b9Schristos     /*
615a53f50b9Schristos      * local automounter mounts do not allocate a special address, so
616a53f50b9Schristos      * no need to XFREE(nfs_args.addr) under TLI.
617a53f50b9Schristos      */
618a53f50b9Schristos #endif /* HAVE_TRANSPORT_TYPE_TLI */
619a53f50b9Schristos 
620a53f50b9Schristos #ifdef HAVE_FS_AUTOFS
621a53f50b9Schristos   } else {
622a53f50b9Schristos     /* This is it!  Here we try to mount amd on its mount points */
623a53f50b9Schristos     error = mount_fs(&mnt, genflags, (caddr_t) mp->am_autofs_fh,
624a53f50b9Schristos 		     retry, type, 0, NULL, mnttab_file_name, on_autofs);
625a53f50b9Schristos #endif /* HAVE_FS_AUTOFS */
626a53f50b9Schristos   }
627a53f50b9Schristos   if (error == 0 || forced_unmount)
628a53f50b9Schristos      return error;
629a53f50b9Schristos 
630a53f50b9Schristos   /*
631a53f50b9Schristos    * If user wants forced/lazy unmount semantics, then try it iff the
632a53f50b9Schristos    * current mount failed with EIO or ESTALE.
633a53f50b9Schristos    */
634a53f50b9Schristos   if (gopt.flags & CFM_FORCED_UNMOUNTS) {
635a53f50b9Schristos     switch (errno) {
636a53f50b9Schristos     case ESTALE:
637a53f50b9Schristos     case EIO:
638a53f50b9Schristos       forced_unmount = errno;
639a53f50b9Schristos       plog(XLOG_WARNING, "Mount %s failed (%m); force unmount.", mp->am_path);
640a53f50b9Schristos       if ((error = UMOUNT_FS(mp->am_path, mnttab_file_name,
641a53f50b9Schristos 			     AMU_UMOUNT_FORCE | AMU_UMOUNT_DETACH)) < 0) {
642a53f50b9Schristos 	plog(XLOG_WARNING, "Forced umount %s failed: %m.", mp->am_path);
643a53f50b9Schristos 	errno = forced_unmount;
644a53f50b9Schristos       } else
645a53f50b9Schristos 	goto again;
646a53f50b9Schristos     default:
647a53f50b9Schristos       break;
648a53f50b9Schristos     }
649a53f50b9Schristos   }
650a53f50b9Schristos 
651a53f50b9Schristos   return error;
652a53f50b9Schristos }
653a53f50b9Schristos 
654a53f50b9Schristos 
655a53f50b9Schristos void
am_unmounted(am_node * mp)656a53f50b9Schristos am_unmounted(am_node *mp)
657a53f50b9Schristos {
658*8bae5d40Schristos   mntfs *mf = mp->am_al->al_mnt;
659a53f50b9Schristos 
6604bcd344eSchristos   if (!foreground) {		/* firewall - should never happen */
6614bcd344eSchristos     /*
6624bcd344eSchristos      * This is a coding error.  Make sure we hear about it!
6634bcd344eSchristos      */
6644bcd344eSchristos     plog(XLOG_FATAL, "am_unmounted: illegal use in background (%s)",
6654bcd344eSchristos 	mp->am_name);
6664bcd344eSchristos     notify_child(mp, AMQ_UMNT_OK, 0, 0);	/* XXX - be safe? */
667a53f50b9Schristos     return;
6684bcd344eSchristos   }
669a53f50b9Schristos 
670a53f50b9Schristos   /*
671a53f50b9Schristos    * Do unmounted callback
672a53f50b9Schristos    */
673a53f50b9Schristos   if (mf->mf_ops->umounted)
674a53f50b9Schristos     mf->mf_ops->umounted(mf);
675a53f50b9Schristos 
676a53f50b9Schristos   /*
677a53f50b9Schristos    * This is ugly, but essentially unavoidable.
678a53f50b9Schristos    * Sublinks must be treated separately as type==link
679a53f50b9Schristos    * when the base type is different.
680a53f50b9Schristos    */
681a53f50b9Schristos   if (mp->am_link && mf->mf_ops != &amfs_link_ops)
682a53f50b9Schristos     amfs_link_ops.umount_fs(mp, mf);
683a53f50b9Schristos 
684a53f50b9Schristos #ifdef HAVE_FS_AUTOFS
685a53f50b9Schristos   if (mf->mf_flags & MFF_IS_AUTOFS)
686a53f50b9Schristos     autofs_release_fh(mp);
687a53f50b9Schristos   if (mp->am_flags & AMF_AUTOFS)
688a53f50b9Schristos     autofs_umount_succeeded(mp);
689a53f50b9Schristos #endif /* HAVE_FS_AUTOFS */
690a53f50b9Schristos 
691a53f50b9Schristos   /*
692a53f50b9Schristos    * Clean up any directories that were made
693a53f50b9Schristos    *
694a53f50b9Schristos    * If we remove the mount point of a pending mount, any queued access
695a53f50b9Schristos    * to it will fail. So don't do it in that case.
696a53f50b9Schristos    * Also don't do it if the refcount is > 1.
697a53f50b9Schristos    */
698a53f50b9Schristos   if (mf->mf_flags & MFF_MKMNT &&
699a53f50b9Schristos       mf->mf_refc == 1 &&
700a53f50b9Schristos       !(mp->am_flags & AMF_REMOUNT)) {
701a53f50b9Schristos     plog(XLOG_INFO, "removing mountpoint directory '%s'", mf->mf_mount);
702a53f50b9Schristos     rmdirs(mf->mf_mount);
703a53f50b9Schristos     mf->mf_flags &= ~MFF_MKMNT;
704a53f50b9Schristos   }
705a53f50b9Schristos 
706a53f50b9Schristos   /*
707a53f50b9Schristos    * If this is a pseudo-directory then adjust the link count
708a53f50b9Schristos    * in the parent
709a53f50b9Schristos    */
710a53f50b9Schristos   if (mp->am_parent && mp->am_fattr.na_type == NFDIR)
711a53f50b9Schristos     --mp->am_parent->am_fattr.na_nlink;
712a53f50b9Schristos 
713a53f50b9Schristos   /*
714a53f50b9Schristos    * Update mtime of parent node
715a53f50b9Schristos    */
716*8bae5d40Schristos   if (mp->am_parent && mp->am_parent->am_al->al_mnt)
717a53f50b9Schristos     clocktime(&mp->am_parent->am_fattr.na_mtime);
718a53f50b9Schristos 
719a53f50b9Schristos   if (mp->am_parent && (mp->am_flags & AMF_REMOUNT)) {
720*8bae5d40Schristos     char *fname = xstrdup(mp->am_name);
721a53f50b9Schristos     am_node *mp_parent = mp->am_parent;
722*8bae5d40Schristos     mntfs *mf_parent = mp_parent->am_al->al_mnt;
7234bcd344eSchristos     am_node fake_mp;
724a53f50b9Schristos     int error = 0;
725a53f50b9Schristos 
7264bcd344eSchristos     /*
7274bcd344eSchristos      * We need to use notify_child() after free_map(), so save enough
7284bcd344eSchristos      * to do that in fake_mp.
7294bcd344eSchristos      */
7304bcd344eSchristos     fake_mp.am_fd[1] = mp->am_fd[1];
7314bcd344eSchristos     mp->am_fd[1] = -1;
7324bcd344eSchristos 
733a53f50b9Schristos     free_map(mp);
734a53f50b9Schristos     plog(XLOG_INFO, "am_unmounted: remounting %s", fname);
735a53f50b9Schristos     mp = mf_parent->mf_ops->lookup_child(mp_parent, fname, &error, VLOOK_CREATE);
736a53f50b9Schristos     if (mp && error < 0)
737*8bae5d40Schristos       (void)mf_parent->mf_ops->mount_child(mp, &error);
738a53f50b9Schristos     if (error > 0) {
739a53f50b9Schristos       errno = error;
740a53f50b9Schristos       plog(XLOG_ERROR, "am_unmounted: could not remount %s: %m", fname);
7414bcd344eSchristos       notify_child(&fake_mp, AMQ_UMNT_OK, 0, 0);
7424bcd344eSchristos     } else {
7434bcd344eSchristos       notify_child(&fake_mp, AMQ_UMNT_FAILED, EBUSY, 0);
744a53f50b9Schristos     }
745a53f50b9Schristos     XFREE(fname);
7464bcd344eSchristos   } else {
747a53f50b9Schristos     /*
748a53f50b9Schristos      * We have a race here.
749a53f50b9Schristos      * If this node has a pending mount and amd is going down (unmounting
750a53f50b9Schristos      * everything in the process), then we could potentially free it here
751a53f50b9Schristos      * while a struct continuation still has a reference to it. So when
752a53f50b9Schristos      * amfs_cont is called, it blows up.
753a53f50b9Schristos      * We avoid the race by refusing to free any nodes that have
754*8bae5d40Schristos      * pending mounts (defined as having a non-NULL am_alarray).
755a53f50b9Schristos      */
7564bcd344eSchristos     notify_child(mp, AMQ_UMNT_OK, 0, 0);	/* do this regardless */
757*8bae5d40Schristos     if (!mp->am_alarray)
758a53f50b9Schristos       free_map(mp);
759a53f50b9Schristos   }
7604bcd344eSchristos }
761a53f50b9Schristos 
762a53f50b9Schristos 
763a53f50b9Schristos /*
764a53f50b9Schristos  * Fork the automounter
765a53f50b9Schristos  *
766a53f50b9Schristos  * TODO: Need a better strategy for handling errors
767a53f50b9Schristos  */
768a53f50b9Schristos static int
dofork(void)769a53f50b9Schristos dofork(void)
770a53f50b9Schristos {
771a53f50b9Schristos   int pid;
772a53f50b9Schristos 
773a53f50b9Schristos top:
774a53f50b9Schristos   pid = fork();
775a53f50b9Schristos 
776a53f50b9Schristos   if (pid < 0) {		/* fork error, retry in 1 second */
777a53f50b9Schristos     sleep(1);
778a53f50b9Schristos     goto top;
779a53f50b9Schristos   }
780a53f50b9Schristos   if (pid == 0) {		/* child process (foreground==false) */
781a53f50b9Schristos     am_set_mypid();
782a53f50b9Schristos     foreground = 0;
783a53f50b9Schristos   } else {			/* parent process, has one more child */
784a53f50b9Schristos     NumChildren++;
785a53f50b9Schristos   }
786a53f50b9Schristos 
787a53f50b9Schristos   return pid;
788a53f50b9Schristos }
789a53f50b9Schristos 
790a53f50b9Schristos 
791a53f50b9Schristos int
background(void)792a53f50b9Schristos background(void)
793a53f50b9Schristos {
794a53f50b9Schristos   int pid = dofork();
795a53f50b9Schristos 
796a53f50b9Schristos   if (pid == 0) {
797a53f50b9Schristos     dlog("backgrounded");
798a53f50b9Schristos     foreground = 0;
799a53f50b9Schristos   } else
800a53f50b9Schristos     dlog("forked process %d", pid);
801a53f50b9Schristos   return pid;
802a53f50b9Schristos }
803