2010-03-08 13:19:14 +03:00
|
|
|
/* $NetBSD: pack.c,v 1.8 2010/03/08 10:19:14 pooka Exp $ */
|
1996-03-03 20:21:25 +03:00
|
|
|
|
1996-03-17 09:29:19 +03:00
|
|
|
/*
|
1995-04-28 10:54:58 +04:00
|
|
|
* Copyright (c) 1992, 1993
|
|
|
|
* The Regents of the University of California. All rights reserved.
|
|
|
|
*
|
|
|
|
* This software was developed by the Computer Systems Engineering group
|
|
|
|
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
|
|
|
|
* contributed to Berkeley.
|
|
|
|
*
|
|
|
|
* All advertising materials mentioning features or use of this software
|
|
|
|
* must display the following acknowledgement:
|
|
|
|
* This product includes software developed by the University of
|
|
|
|
* California, Lawrence Berkeley Laboratories.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
2003-08-07 15:25:11 +04:00
|
|
|
* 3. Neither the name of the University nor the names of its contributors
|
1995-04-28 10:54:58 +04:00
|
|
|
* may be used to endorse or promote products derived from this software
|
|
|
|
* without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* from: @(#)pack.c 8.1 (Berkeley) 6/6/93
|
|
|
|
*/
|
|
|
|
|
2004-06-21 02:20:14 +04:00
|
|
|
#if HAVE_NBTOOL_CONFIG_H
|
|
|
|
#include "nbtool_config.h"
|
|
|
|
#endif
|
|
|
|
|
1995-04-28 10:54:58 +04:00
|
|
|
#include <sys/param.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2006-08-26 22:17:13 +04:00
|
|
|
#include <util.h>
|
2002-01-29 13:20:28 +03:00
|
|
|
#include "defs.h"
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Packing. We have three separate kinds of packing here.
|
|
|
|
*
|
2002-09-26 08:07:35 +04:00
|
|
|
* First, we pack device instances which have identical parent specs.
|
1995-04-28 10:54:58 +04:00
|
|
|
*
|
|
|
|
* Second, we pack locators. Given something like
|
|
|
|
*
|
|
|
|
* hp0 at mba0 drive 0
|
|
|
|
* hp* at mba* drive ?
|
|
|
|
* ht0 at mba0 drive 0
|
|
|
|
* tu0 at ht0 slave 0
|
|
|
|
* ht* at mba* drive ?
|
|
|
|
* tu* at ht* slave ?
|
|
|
|
*
|
|
|
|
* (where the default drive and slave numbers are -1), we have three
|
|
|
|
* locators whose value is 0 and three whose value is -1. Rather than
|
|
|
|
* emitting six integers, we emit just two.
|
|
|
|
*
|
|
|
|
* When packing locators, we would like to find sequences such as
|
|
|
|
* {1 2 3} {2 3 4} {3} {4 5}
|
|
|
|
* and turn this into the flat sequence {1 2 3 4 5}, with each subsequence
|
|
|
|
* given by the appropriate offset (here 0, 1, 2, and 3 respectively).
|
|
|
|
* Non-overlapping packing is much easier, and so we use that here
|
|
|
|
* and miss out on the chance to squeeze the locator sequence optimally.
|
|
|
|
* (So it goes.)
|
|
|
|
*/
|
|
|
|
|
2000-10-02 23:48:34 +04:00
|
|
|
typedef int (*vec_cmp_func)(const void *, int, int);
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
#define TAILHSIZE 128
|
|
|
|
#define PVHASH(i) ((i) & (TAILHSIZE - 1))
|
|
|
|
#define LOCHASH(l) (((long)(l) >> 2) & (TAILHSIZE - 1))
|
|
|
|
struct tails {
|
|
|
|
struct tails *t_next;
|
|
|
|
int t_ends_at;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct tails *tails[TAILHSIZE];
|
|
|
|
static int locspace;
|
|
|
|
|
2000-10-02 23:48:34 +04:00
|
|
|
static void packdevi(void);
|
|
|
|
static void packlocs(void);
|
|
|
|
|
|
|
|
static int sameas(struct devi *, struct devi *);
|
|
|
|
static int findvec(const void *, int, int, vec_cmp_func, int);
|
|
|
|
static int samelocs(const void *, int, int);
|
|
|
|
static int addlocs(const char **, int);
|
|
|
|
static int loclencmp(const void *, const void *);
|
|
|
|
static void resettails(void);
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
void
|
2000-10-02 23:48:34 +04:00
|
|
|
pack(void)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
2002-09-26 08:07:35 +04:00
|
|
|
struct pspec *p;
|
1997-10-18 11:58:56 +04:00
|
|
|
struct devi *i;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
/* Pack instances and make parent vectors. */
|
|
|
|
packdevi();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now that we know what we have, find upper limits on space
|
2002-09-26 08:07:35 +04:00
|
|
|
* needed for the loc[] table. The loc table size is bounded by
|
1995-04-28 10:54:58 +04:00
|
|
|
* what we would get if no packing occurred.
|
|
|
|
*/
|
2002-09-26 08:07:35 +04:00
|
|
|
locspace = 0;
|
2002-06-05 14:56:17 +04:00
|
|
|
TAILQ_FOREACH(i, &alldevi, i_next) {
|
2005-10-02 03:30:37 +04:00
|
|
|
if (!i->i_active == DEVI_ACTIVE || i->i_collapsed)
|
1995-04-28 10:54:58 +04:00
|
|
|
continue;
|
2002-09-26 08:07:35 +04:00
|
|
|
if ((p = i->i_pspec) == NULL)
|
|
|
|
continue;
|
|
|
|
locspace += p->p_iattr->a_loclen;
|
1995-04-28 10:54:58 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate and pack loc[]. */
|
2007-01-14 02:47:36 +03:00
|
|
|
locators.vec = ecalloc((size_t)locspace, sizeof(*locators.vec));
|
1995-04-28 10:54:58 +04:00
|
|
|
locators.used = 0;
|
|
|
|
packlocs();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2002-09-26 08:07:35 +04:00
|
|
|
* Pack device instances together wherever possible.
|
1995-04-28 10:54:58 +04:00
|
|
|
*/
|
2010-01-21 21:06:38 +03:00
|
|
|
static void
|
2000-10-02 23:48:34 +04:00
|
|
|
packdevi(void)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
1997-04-17 09:01:09 +04:00
|
|
|
struct devi *firststar, *i, **ip, *l, *p;
|
|
|
|
struct devbase *d;
|
|
|
|
int j, m, n;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sort all the cloning units to after the non-cloning units,
|
|
|
|
* preserving order of cloning and non-cloning units with
|
1999-12-20 20:19:13 +03:00
|
|
|
* respect to other units of the same type.
|
1997-04-17 09:01:09 +04:00
|
|
|
*
|
|
|
|
* Algorithm: Walk down the list until the first cloning unit is
|
|
|
|
* seen for the second time (or until the end of the list, if there
|
|
|
|
* are no cloning units on the list), moving starred units to the
|
|
|
|
* end of the list.
|
|
|
|
*/
|
2002-06-05 14:56:17 +04:00
|
|
|
TAILQ_FOREACH(d, &allbases, d_next) {
|
1997-04-17 09:01:09 +04:00
|
|
|
ip = &d->d_ihead;
|
|
|
|
firststar = NULL;
|
|
|
|
|
|
|
|
for (i = *ip; i != firststar; i = *ip) {
|
|
|
|
if (i->i_unit != STAR) {
|
|
|
|
/* try i->i_bsame next */
|
|
|
|
ip = &i->i_bsame;
|
|
|
|
} else {
|
|
|
|
if (firststar == NULL)
|
|
|
|
firststar = i;
|
|
|
|
|
|
|
|
*d->d_ipp = i;
|
|
|
|
d->d_ipp = &i->i_bsame;
|
|
|
|
|
|
|
|
*ip = i->i_bsame;
|
|
|
|
i->i_bsame = NULL;
|
|
|
|
|
|
|
|
/* leave ip alone; try (old) i->i_bsame next */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
1995-04-28 10:54:58 +04:00
|
|
|
|
2007-01-14 02:47:36 +03:00
|
|
|
packed = ecalloc((size_t)ndevi + 1, sizeof *packed);
|
1995-04-28 10:54:58 +04:00
|
|
|
n = 0;
|
2002-06-05 14:56:17 +04:00
|
|
|
TAILQ_FOREACH(d, &allbases, d_next) {
|
1995-04-28 10:54:58 +04:00
|
|
|
/*
|
|
|
|
* For each instance of each device, add or collapse
|
|
|
|
* all its aliases.
|
Rework the way orphan device instances are handled. To achieve that, keep
track of instances attaching at root, and walk down the tree of active
device instances. Then, all instances that are not marked active are
found as orphans.
Doing it that way allows us to simply ignore orphan devices, instead of
warning about them and still keep them in the configuration. Now, orphaned
instances are considered as never having existed.
In the end, this allows 'no <device> at <attachment>' to be much more
efficient, as the user doesn't have to negate all descendents of the
instance s/he actually wants to negate. Warnings are still emitted,
though.
While there, make official a side-effect of the previous lack of action
against orphaned instances: config(1) used to warn about instances that
attach at a numbered device when no instance of that device with that
number existed, even though there was a starred instance of the device.
E.g. (provided by Alan Barrett):
pciide* at pci? dev ? function ? flags 0x0000
wdc0 at isa? port 0x1f0 irq 14 flags 0x00
wdc1 at isa? port 0x170 irq 15 flags 0x00
atabus* at ata?
wd0 at atabus0 drive 0
With this commit, config(1) will no longer warn about 'wd0 at atabus0'.
2005-10-01 02:36:20 +04:00
|
|
|
*
|
|
|
|
* Pseudo-devices have a non-empty d_ihead for convenience.
|
|
|
|
* Ignore them.
|
1995-04-28 10:54:58 +04:00
|
|
|
*/
|
Rework the way orphan device instances are handled. To achieve that, keep
track of instances attaching at root, and walk down the tree of active
device instances. Then, all instances that are not marked active are
found as orphans.
Doing it that way allows us to simply ignore orphan devices, instead of
warning about them and still keep them in the configuration. Now, orphaned
instances are considered as never having existed.
In the end, this allows 'no <device> at <attachment>' to be much more
efficient, as the user doesn't have to negate all descendents of the
instance s/he actually wants to negate. Warnings are still emitted,
though.
While there, make official a side-effect of the previous lack of action
against orphaned instances: config(1) used to warn about instances that
attach at a numbered device when no instance of that device with that
number existed, even though there was a starred instance of the device.
E.g. (provided by Alan Barrett):
pciide* at pci? dev ? function ? flags 0x0000
wdc0 at isa? port 0x1f0 irq 14 flags 0x00
wdc1 at isa? port 0x170 irq 15 flags 0x00
atabus* at ata?
wd0 at atabus0 drive 0
With this commit, config(1) will no longer warn about 'wd0 at atabus0'.
2005-10-01 02:36:20 +04:00
|
|
|
if (d->d_ispseudo)
|
|
|
|
continue;
|
1995-04-28 10:54:58 +04:00
|
|
|
for (i = d->d_ihead; i != NULL; i = i->i_bsame) {
|
|
|
|
m = n;
|
|
|
|
for (l = i; l != NULL; l = l->i_alias) {
|
2010-03-08 13:19:14 +03:00
|
|
|
if (l->i_active != DEVI_ACTIVE
|
|
|
|
|| i->i_pseudoroot)
|
Rework the way orphan device instances are handled. To achieve that, keep
track of instances attaching at root, and walk down the tree of active
device instances. Then, all instances that are not marked active are
found as orphans.
Doing it that way allows us to simply ignore orphan devices, instead of
warning about them and still keep them in the configuration. Now, orphaned
instances are considered as never having existed.
In the end, this allows 'no <device> at <attachment>' to be much more
efficient, as the user doesn't have to negate all descendents of the
instance s/he actually wants to negate. Warnings are still emitted,
though.
While there, make official a side-effect of the previous lack of action
against orphaned instances: config(1) used to warn about instances that
attach at a numbered device when no instance of that device with that
number existed, even though there was a starred instance of the device.
E.g. (provided by Alan Barrett):
pciide* at pci? dev ? function ? flags 0x0000
wdc0 at isa? port 0x1f0 irq 14 flags 0x00
wdc1 at isa? port 0x170 irq 15 flags 0x00
atabus* at ata?
wd0 at atabus0 drive 0
With this commit, config(1) will no longer warn about 'wd0 at atabus0'.
2005-10-01 02:36:20 +04:00
|
|
|
continue;
|
1995-04-28 10:54:58 +04:00
|
|
|
l->i_locoff = -1;
|
|
|
|
/* try to find an equivalent for l */
|
|
|
|
for (j = m; j < n; j++) {
|
|
|
|
p = packed[j];
|
|
|
|
if (sameas(l, p)) {
|
|
|
|
l->i_collapsed = 1;
|
|
|
|
l->i_cfindex = p->i_cfindex;
|
|
|
|
goto nextalias;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* could not find a suitable alias */
|
|
|
|
l->i_collapsed = 0;
|
|
|
|
l->i_cfindex = n;
|
|
|
|
packed[n++] = l;
|
2002-06-05 14:56:17 +04:00
|
|
|
nextalias:;
|
1995-04-28 10:54:58 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
npacked = n;
|
|
|
|
packed[n] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return true if two aliases are "the same". In this case, they need
|
2002-09-26 08:07:35 +04:00
|
|
|
* to have the same parent spec, have the same config flags, and have
|
|
|
|
* the same locators.
|
1995-04-28 10:54:58 +04:00
|
|
|
*/
|
|
|
|
static int
|
2000-10-02 23:48:34 +04:00
|
|
|
sameas(struct devi *i1, struct devi *i2)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
1997-10-18 11:58:56 +04:00
|
|
|
const char **p1, **p2;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
2002-09-26 08:07:35 +04:00
|
|
|
if (i1->i_pspec != i2->i_pspec)
|
1996-06-17 22:21:35 +04:00
|
|
|
return (0);
|
1995-04-28 10:54:58 +04:00
|
|
|
if (i1->i_cfflags != i2->i_cfflags)
|
|
|
|
return (0);
|
|
|
|
for (p1 = i1->i_locs, p2 = i2->i_locs; *p1 == *p2; p2++)
|
|
|
|
if (*p1++ == 0)
|
|
|
|
return (1);
|
2002-09-26 08:07:35 +04:00
|
|
|
return (0);
|
1995-04-28 10:54:58 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2000-10-02 23:48:34 +04:00
|
|
|
packlocs(void)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
2002-09-26 08:07:35 +04:00
|
|
|
struct pspec *ps;
|
1997-10-18 11:58:56 +04:00
|
|
|
struct devi **p, *i;
|
2001-07-01 06:46:47 +04:00
|
|
|
int l,o;
|
|
|
|
extern int Pflag;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
qsort(packed, npacked, sizeof *packed, loclencmp);
|
|
|
|
for (p = packed; (i = *p) != NULL; p++) {
|
2002-09-26 08:07:35 +04:00
|
|
|
if ((ps = i->i_pspec) != NULL &&
|
|
|
|
(l = ps->p_iattr->a_loclen) > 0) {
|
2001-07-01 06:46:47 +04:00
|
|
|
if (Pflag) {
|
|
|
|
o = findvec(i->i_locs,
|
|
|
|
LOCHASH(i->i_locs[l - 1]), l,
|
1995-04-28 10:54:58 +04:00
|
|
|
samelocs, locators.used);
|
2001-07-01 06:46:47 +04:00
|
|
|
i->i_locoff = o < 0 ?
|
|
|
|
addlocs(i->i_locs, l) : o;
|
|
|
|
} else
|
|
|
|
i->i_locoff = addlocs(i->i_locs, l);
|
1995-04-28 10:54:58 +04:00
|
|
|
} else
|
|
|
|
i->i_locoff = -1;
|
|
|
|
}
|
|
|
|
resettails();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the index at which the given vector already exists, or -1
|
|
|
|
* if it is not anywhere in the current set. If we return -1, we assume
|
|
|
|
* our caller will add it at the end of the current set, and we make
|
|
|
|
* sure that next time, we will find it there.
|
|
|
|
*/
|
|
|
|
static int
|
2000-10-02 23:48:34 +04:00
|
|
|
findvec(const void *ptr, int hash, int len, vec_cmp_func cmp, int nextplace)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
1997-10-18 11:58:56 +04:00
|
|
|
struct tails *t, **hp;
|
|
|
|
int off;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
hp = &tails[hash];
|
|
|
|
for (t = *hp; t != NULL; t = t->t_next) {
|
|
|
|
off = t->t_ends_at - len;
|
|
|
|
if (off >= 0 && (*cmp)(ptr, off, len))
|
|
|
|
return (off);
|
|
|
|
}
|
2003-11-25 00:44:37 +03:00
|
|
|
t = ecalloc(1, sizeof(*t));
|
1995-04-28 10:54:58 +04:00
|
|
|
t->t_next = *hp;
|
|
|
|
*hp = t;
|
|
|
|
t->t_ends_at = nextplace + len;
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Comparison function for locators.
|
|
|
|
*/
|
|
|
|
static int
|
2000-10-02 23:48:34 +04:00
|
|
|
samelocs(const void *ptr, int off, int len)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
2009-04-11 16:41:10 +04:00
|
|
|
const char * const *p, * const *q;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
2009-04-11 16:41:10 +04:00
|
|
|
for (p = &locators.vec[off], q = (const char * const *)ptr; --len >= 0;)
|
1995-04-28 10:54:58 +04:00
|
|
|
if (*p++ != *q++)
|
|
|
|
return (0); /* different */
|
|
|
|
return (1); /* same */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the given locators at the end of the global loc[] table.
|
|
|
|
*/
|
|
|
|
static int
|
2000-10-02 23:48:34 +04:00
|
|
|
addlocs(const char **locs, int len)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
1997-10-18 11:58:56 +04:00
|
|
|
const char **p;
|
|
|
|
int ret;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
ret = locators.used;
|
|
|
|
if ((locators.used = ret + len) > locspace)
|
|
|
|
panic("addlocs: overrun");
|
|
|
|
for (p = &locators.vec[ret]; --len >= 0;)
|
|
|
|
*p++ = *locs++;
|
|
|
|
return (ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Comparison function for qsort-by-locator-length, longest first.
|
|
|
|
* We rashly assume that subtraction of these lengths does not overflow.
|
|
|
|
*/
|
|
|
|
static int
|
2000-10-02 23:48:34 +04:00
|
|
|
loclencmp(const void *a, const void *b)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
2007-01-14 02:47:36 +03:00
|
|
|
const struct pspec *p1, *p2;
|
1997-10-18 11:58:56 +04:00
|
|
|
int l1, l2;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
2009-04-11 16:41:10 +04:00
|
|
|
p1 = (*(const struct devi * const *)a)->i_pspec;
|
2002-09-26 08:07:35 +04:00
|
|
|
l1 = p1 != NULL ? p1->p_iattr->a_loclen : 0;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
2009-04-11 16:41:10 +04:00
|
|
|
p2 = (*(const struct devi * const *)b)->i_pspec;
|
2002-09-26 08:07:35 +04:00
|
|
|
l2 = p2 != NULL ? p2->p_iattr->a_loclen : 0;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
return (l2 - l1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2000-10-02 23:48:34 +04:00
|
|
|
resettails(void)
|
1995-04-28 10:54:58 +04:00
|
|
|
{
|
1997-10-18 11:58:56 +04:00
|
|
|
struct tails **p, *t, *next;
|
|
|
|
int i;
|
1995-04-28 10:54:58 +04:00
|
|
|
|
|
|
|
for (p = tails, i = TAILHSIZE; --i >= 0; p++) {
|
|
|
|
for (t = *p; t != NULL; t = next) {
|
|
|
|
next = t->t_next;
|
|
|
|
free(t);
|
|
|
|
}
|
|
|
|
*p = NULL;
|
|
|
|
}
|
|
|
|
}
|