1 | #@file ogfunctions.lib |
---|
2 | #@brief Librería o clase para la generación del 1erFS |
---|
3 | #@class client |
---|
4 | #@brief Funciones para la generación del primers sistema (initrd) |
---|
5 | #@version 0.91 |
---|
6 | #@warning License: GNU GPLv3+ |
---|
7 | |
---|
8 | |
---|
9 | |
---|
10 | |
---|
11 | ogExportKernelParameters () |
---|
12 | { |
---|
13 | GLOBAL="cat /proc/cmdline" |
---|
14 | for i in `${GLOBAL}` |
---|
15 | do |
---|
16 | echo $i | grep "=" > /dev/null && export $i |
---|
17 | done |
---|
18 | return 0 |
---|
19 | } |
---|
20 | |
---|
21 | ogExportVarEnvironment () |
---|
22 | { |
---|
23 | export CFGINITRD="/tmp/initrd.cfg" |
---|
24 | echo "puntos de accesos al servidor ogprotocol=nfs" >> $CFGINITRD |
---|
25 | export NFSROOTBOOT="/var/lib/tftpboot" && echo "NFSROOTBOOT=$NFSROOTBOOT" >> $CFGINITRD |
---|
26 | export NFSCLIENTDIR="/opt/opengnsys/client" && echo "NFSCLIENDIR=$NFSCLIENTDIR" >> $CFGINITRD |
---|
27 | export NFSLOGDIR="/opt/opengnsys/log/clients" && echo "NFSLOGDIR=$NFSLOGDIR" >> $CFGINITRD |
---|
28 | export NFSIMGDIR="/opt/opengnsys/images" && echo "NFSIMGDIR=$NFSIMGDIR" >> $CFGINITRD |
---|
29 | echo "puntos de accesos al servidor ogprotocol=smb" >> $CFGINITRD |
---|
30 | export SMBROOTBOOT="tftpboot" && echo "SMBROOTBOOT=$SMBROOTBOOT" >> $CFGINITRD |
---|
31 | export SMBCLIENTDIR="ogclient" && echo "SMBCLIENTDIR=$SMBCLIENTDIR" >> $CFGINITRD |
---|
32 | export SMBLOGDIR="oglog" && echo "SMBLOGDIR=$SMBLOGDIR" >> $CFGINITRD |
---|
33 | export SMBIMGDIR="ogimages" && echo "SMBIMGDIR=$SMBIMGDIR" >> $CFGINITRD |
---|
34 | |
---|
35 | echo "puntos de montaje local para los contenedores" >> $CFGINITRD |
---|
36 | export LOCALROOTBOOT="/opt/og2fs/tftpboot" && echo "LOCALROOTBOOT=$LOCALROOTBOOT" >> $CFGINITRD |
---|
37 | export LOCALCLIENTDIR="/opt/opengnsys" && echo "LOCALCLIENTDIR=$LOCALCLIENTDIR" >> $CFGINITRD |
---|
38 | export LOCALLOGDIR="/opt/opengnsys/log" && echo "LOCALLOGDIR=$LOCALLOGDIR" >> $CFGINITRD |
---|
39 | export LOCALIMGDIR="/opt/opengnsys/images" && echo "LOCALIMGDIR=$LOCALIMGDIR" >> $CFGINITRD |
---|
40 | |
---|
41 | echo "puntos de montajes para realizar la integración unionfs entre el initrd y el sistema root" >> $CFGINITRD |
---|
42 | #punto de montaje donde se accede al 2nd FS mediante loop |
---|
43 | export LOCALROOTIMG="/opt/og2fs/2ndfs" && echo "LOCALROOTIMG=$LOCALROOTIMG" >> $CFGINITRD |
---|
44 | #punto de montaje para unionfs |
---|
45 | export LOCALROOTRAM="/opt/og2fs/1stfs" && echo "LOCALROOTRAM=$LOCALROOTRAM" >> $CFGINITRD |
---|
46 | #punto de union entreo LOCALROOTIMG y LOCALROOTRAM |
---|
47 | export LOCALROOTUNION="/opt/og2fs/unionfs" && echo "LOCALROOTUNION=$LOCALROOTUNION" >> $CFGINITRD |
---|
48 | ##INFORMACION DE OTRAS VARIABLES OBTENDIAS EN OTRAS FUNCIONES. |
---|
49 | #ogGetROOTSERVER() ip del servidor pxe, valor obtenido automaticamente desde dhcpd. |
---|
50 | #IPV4DDR |
---|
51 | #IPV4BROADCAST |
---|
52 | #IPV4NETMASK |
---|
53 | #IPV4GATEWAY |
---|
54 | #DNS0 DNS1 |
---|
55 | #HOSTNAME |
---|
56 | #ROOTSERVER #ip del servidor pxe que ha servido el kernel |
---|
57 | #REPOSERVER=ogrepo -> ogConectROOTSERVER() ip del servidor de images para separar serviicios. |
---|
58 | return 0 |
---|
59 | } |
---|
60 | |
---|
61 | ogConfigureRamfs () |
---|
62 | { |
---|
63 | mkdir -p $LOCALROOTBOOT |
---|
64 | mkdir -p $LOCALROOTIMG |
---|
65 | mkdir -p $LOCALROOTRAM |
---|
66 | mkdir -p $LOCALROOTUNION |
---|
67 | } |
---|
68 | |
---|
69 | ogLoadNetModule () |
---|
70 | { |
---|
71 | #cargando netmodule |
---|
72 | if [ -n "$ognetmodule" ] |
---|
73 | then |
---|
74 | echo "Cargando modulo de red $netmodule" |
---|
75 | insmod `find /lib/modules/ -name ${netmodule}*` |
---|
76 | fi |
---|
77 | } |
---|
78 | |
---|
79 | |
---|
80 | ogPostConfigureFS() |
---|
81 | { |
---|
82 | # configuramos el /etc/hostname. |
---|
83 | echo $HOSTNAME > /etc/hostname |
---|
84 | |
---|
85 | #configuramos el /etc/hosts |
---|
86 | echo "127.0.0.1 localhost" > /etc/hosts |
---|
87 | echo "$IPV4ADDR $HOSTNAME" >> /etc/hosts |
---|
88 | |
---|
89 | #configuramos el host.conf |
---|
90 | echo "order hosts,bind" > /etc/host.conf |
---|
91 | echo "multi on" >> /etc/host.conf |
---|
92 | |
---|
93 | # configuramos el /etc/networks |
---|
94 | #read -e NETIP NETDEFAULT <<<$(route -n | grep eth0 | awk -F" " '{print $1}') |
---|
95 | NETIP=$(route -n | grep eth0 | awk -F" " '{print $1}') && NETIP=$(echo $NETIP | cut -f1 -d" ") |
---|
96 | echo "default 0.0.0.0" > /etc/networks |
---|
97 | echo "loopback 127.0.0.0" >> /etc/networks |
---|
98 | echo "link-local 169.254.0.0" >> /etc/networks |
---|
99 | echo "localnet $NETIP" >> /etc/networks |
---|
100 | #route |
---|
101 | } |
---|
102 | |
---|
103 | |
---|
104 | |
---|
105 | |
---|
106 | ogGetROOTSERVER () |
---|
107 | { |
---|
108 | # get nfs root from dhcp |
---|
109 | if [ "x${NFSROOT}" = "xauto" ]; then |
---|
110 | # check if server ip is part of dhcp root-path |
---|
111 | if [ "${ROOTPATH#*:}" = "${ROOTPATH}" ]; then |
---|
112 | NFSROOT=${ROOTSERVER}:${ROOTPATH} |
---|
113 | else |
---|
114 | NFSROOT=${ROOTPATH} |
---|
115 | fi |
---|
116 | |
---|
117 | # nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] |
---|
118 | elif [ -n "${NFSROOT}" ]; then |
---|
119 | # nfs options are an optional arg |
---|
120 | if [ "${NFSROOT#*,}" != "${NFSROOT}" ]; then |
---|
121 | NFSOPTS="-o ${NFSROOT#*,}" |
---|
122 | fi |
---|
123 | NFSROOT=${NFSROOT%%,*} |
---|
124 | if [ "${NFSROOT#*:}" = "$NFSROOT" ]; then |
---|
125 | NFSROOT=${ROOTSERVER}:${NFSROOT} |
---|
126 | fi |
---|
127 | fi |
---|
128 | export ROOTSERVER |
---|
129 | echo "ROOTSERVER=$ROOTSERVER" >> $CFGINITRD |
---|
130 | return 0 |
---|
131 | } |
---|
132 | |
---|
133 | ogConectROOTSERVER () |
---|
134 | { |
---|
135 | local OPTIONS |
---|
136 | #params a detectar |
---|
137 | if [ $ogrepo ] |
---|
138 | then |
---|
139 | # Validar si la ip es correcta |
---|
140 | ROOTREPO=$ogrepo |
---|
141 | else |
---|
142 | ROOTREPO=$ROOTSERVER |
---|
143 | fi |
---|
144 | |
---|
145 | case "$ogprotocol" in |
---|
146 | cdrom) |
---|
147 | echo "Montar imagen de CD-ROM" |
---|
148 | blkid /dev/s* |
---|
149 | mount -t iso9660 LABEL=ogClient $LOCALROOTBOOT |
---|
150 | ;; |
---|
151 | httfs) |
---|
152 | echo "protocolo httfs aun no soportado" |
---|
153 | ;; |
---|
154 | sshfs) |
---|
155 | echo "protocolo sshfs aun no soportado" |
---|
156 | ;; |
---|
157 | smb) |
---|
158 | echo "Preparando conexión con el Repositorio $ROOTSERVER por $ogprotocol" |
---|
159 | OPTIONS=" -o user=opengnsys,pass=og" |
---|
160 | mount.cifs //${ROOTSERVER}/${SMBCLIENTDIR} $LOCALCLIENTDIR $OPTIONS |
---|
161 | mount.cifs //${ROOTSERVER}/${SMBLOGDIR} $LOCALLOGDIR $OPTIONS |
---|
162 | mount.cifs //${ROOTSERVER}/${SMBROOTBOOT} $LOCALROOTBOOT $OPTIONS |
---|
163 | mount.cifs //${ROOTREPO}/${SMBIMGDIR} $LOCALIMGDIR ${OPTIONS},ro |
---|
164 | ;; |
---|
165 | nfs) |
---|
166 | echo "Preparando conexión con el Repositorio $ROOTSERVER por $ogprotocol" |
---|
167 | nfsmount -o nolock,ro $ROOTSERVER:$NFSCLIENTDIR $LOCALCLIENTDIR |
---|
168 | nfsmount -o nolock $ROOTSERVER:$NFSLOGDIR $LOCALLOGDIR |
---|
169 | nfsmount -o nolock $ROOTSERVER:$NFSROOTBOOT $LOCALROOTBOOT |
---|
170 | nfsmount -o nolock,ro $ROOTREPO:$NFSIMGDIR $LOCALIMGDIR |
---|
171 | ;; |
---|
172 | esac |
---|
173 | } |
---|
174 | |
---|
175 | ogMerge2ndFile() |
---|
176 | { |
---|
177 | if [ -f $LOCALROOTBOOT/ogclient/ogclient.sqfs ] |
---|
178 | then |
---|
179 | cat /proc/mounts > /tmp/mtab.preunion |
---|
180 | if [ "$og2nd" == "img" ] |
---|
181 | then |
---|
182 | #para acceder al img |
---|
183 | losetup /dev/loop0 $LOCALROOTBOOT/ogclient/ogclient.img -o 32256 |
---|
184 | mount /dev/loop0 $LOCALROOTIMG |
---|
185 | else |
---|
186 | ## para acceder al squashfs |
---|
187 | mount $LOCALROOTBOOT/ogclient/ogclient.sqfs $LOCALROOTIMG -t squashfs -o loop |
---|
188 | fi |
---|
189 | for i in etc var lib bin sbin usr root boot; do |
---|
190 | unionmount $i |
---|
191 | done |
---|
192 | cat /tmp/mtab.preunion > /etc/mtab |
---|
193 | else |
---|
194 | echo "Fichero imagen del cliente no encontrado" |
---|
195 | return 1 |
---|
196 | fi |
---|
197 | } |
---|
198 | |
---|
199 | |
---|
200 | unionmount() |
---|
201 | { |
---|
202 | tmpdir=/$1 #dir |
---|
203 | FUSE_OPT="-o default_permissions -o allow_other -o use_ino -o nonempty -o suid" |
---|
204 | UNION_OPT="-o cow -o noinitgroups" |
---|
205 | UBIN="unionfs-fuse" |
---|
206 | #UPATH="/unionfs" |
---|
207 | #LOCALROOTIMG="/opt/og2fs/2ndfs" |
---|
208 | #LOCALROOTRAM="/opt/og2fs/1stfs" #/unionfs/host #punto de montaje para unionfs |
---|
209 | #LOCALROOTUNION=/opt/og2fs/unionfs/" #/unionfs/union #punto de union entreo LOCALROOTIMG y LOCALROOTRAM |
---|
210 | #mkdir -p $LOCALROOTRAM #/unionfs/host |
---|
211 | #mkdir -p $LOCALROOTUNION #/unionfs/union |
---|
212 | mkdir -p $LOCALROOTRAM$tmpdir |
---|
213 | #mount --bind /$tmpdir $LOCALROOTRAM$tmpdir |
---|
214 | U1STDIR="${LOCALROOTRAM}${tmpdir}=RW" |
---|
215 | U2NDDIR="${LOCALROOTIMG}${tmpdir}=RO" |
---|
216 | UNIONDIR=$LOCALROOTUNION$tmpdir |
---|
217 | mkdir -p $UNIONDIR |
---|
218 | $UBIN $FUSE_OPT $UNION_OPT ${U1STDIR}:${U2NDDIR} $UNIONDIR |
---|
219 | mount --bind $UNIONDIR $tmpdir |
---|
220 | } |
---|
221 | |
---|
222 | |
---|
223 | unionmountOLD() |
---|
224 | { |
---|
225 | FUSE_OPT="-o default_permissions -o allow_other -o use_ino -o nonempty -o suid" |
---|
226 | UNION_OPT="-o cow -o noinitgroups" |
---|
227 | UPATH="/unionfs" |
---|
228 | UBIN="unionfs-fuse" |
---|
229 | mkdir -p /unionfs/host |
---|
230 | mkdir -p /unionfs/union |
---|
231 | dir=$1 |
---|
232 | mkdir -p /unionfs/host/$dir |
---|
233 | #mount --bind /$dir /unionfs/host/$dir |
---|
234 | mkdir -p /unionfs/union/$dir |
---|
235 | host="/unionfs/host/${dir}=RW" |
---|
236 | common="/opt/og2fs/${dir}=RO" |
---|
237 | $UBIN $FUSE_OPT $UNION_OPT ${host}:${common} /unionfs/union/$dir |
---|
238 | mount --bind /unionfs/union/$dir /$dir |
---|
239 | } |
---|
240 | |
---|
241 | ogconfigure_lo() |
---|
242 | { |
---|
243 | # for the portmapper we need localhost |
---|
244 | ifconfig lo 127.0.0.1 |
---|
245 | #/etc/init.d/portmap start |
---|
246 | } |
---|
247 | |
---|
248 | ogconfigure_networking() |
---|
249 | { |
---|
250 | echo "ogconfigure_networking: Buscando interfaz a configurar DEVICE" |
---|
251 | if [ -n "${BOOTIF}" ] |
---|
252 | then |
---|
253 | echo " variable BOOTIF exportada con pxelinux.0 con valor $BOOTIF" |
---|
254 | IP=$IPOPTS |
---|
255 | temp_mac=${BOOTIF#*-} |
---|
256 | # convert to typical mac address format by replacing "-" with ":" |
---|
257 | bootif_mac="" |
---|
258 | IFS='-' |
---|
259 | for x in $temp_mac ; do |
---|
260 | if [ -z "$bootif_mac" ]; then |
---|
261 | bootif_mac="$x" |
---|
262 | else |
---|
263 | bootif_mac="$x:$bootif_mac" |
---|
264 | fi |
---|
265 | done |
---|
266 | unset IFS |
---|
267 | # look for devices with matching mac address, and set DEVICE to |
---|
268 | # appropriate value if match is found. |
---|
269 | for device in /sys/class/net/* ; do |
---|
270 | if [ -f "$device/address" ]; then |
---|
271 | current_mac=$(cat "$device/address") |
---|
272 | if [ "$bootif_mac" = "$current_mac" ]; then |
---|
273 | DEVICE=${device##*/} |
---|
274 | break |
---|
275 | fi |
---|
276 | fi |
---|
277 | done |
---|
278 | else |
---|
279 | echo "variable BOOTIF no exportada, intentamos detectar que interfaz se ha iniciado" |
---|
280 | IP=$ip |
---|
281 | #TODO Detectar que interfaz se ha iniciado |
---|
282 | case ${IP} in |
---|
283 | none|off) |
---|
284 | return 0 |
---|
285 | ;; |
---|
286 | ""|on|any) |
---|
287 | # Bring up device |
---|
288 | DEVICE=eth0 |
---|
289 | ;; |
---|
290 | dhcp|bootp|rarp|both) |
---|
291 | DEVICE=eth0 |
---|
292 | ;; |
---|
293 | *) |
---|
294 | DEVICE=`echo $IP | cut -f6 -d:` |
---|
295 | ;; |
---|
296 | esac |
---|
297 | fi |
---|
298 | if [ -z "${DEVICE}" ]; then |
---|
299 | echo "variable DEVICE con valor $DEVICE no encontrada, llamamos de nuevo a ogconfigure_networking" |
---|
300 | ogconfigure_networking |
---|
301 | fi |
---|
302 | |
---|
303 | if [ -n "${DEVICE}" ] && [ -e /tmp/net-"${DEVICE}".conf ]; then |
---|
304 | echo "variable DEVICE con valor $DEVICE y fichero /tmp/net-$DEVICE encontrados" |
---|
305 | return 0 |
---|
306 | else |
---|
307 | echo "variable DEVICE con valor $DEVICE encontrada, procedemos a configurala y a crear el fichero /tmp/net-$DEVICE" |
---|
308 | fi |
---|
309 | |
---|
310 | # support ip options see linux sources |
---|
311 | # Documentation/filesystems/nfsroot.txt |
---|
312 | # Documentation/frv/booting.txt |
---|
313 | for ROUNDTTT in 2 3 4 6 9 16 25 36 64 100; do |
---|
314 | # The NIC is to be configured if this file does not exist. |
---|
315 | # Ip-Config tries to create this file and when it succeds |
---|
316 | # creating the file, ipconfig is not run again. |
---|
317 | if [ -e /tmp/net-"${DEVICE}".conf ]; then |
---|
318 | break; |
---|
319 | fi |
---|
320 | case ${IP} in |
---|
321 | none|off) |
---|
322 | return 0 |
---|
323 | ;; |
---|
324 | ""|on|any) |
---|
325 | # Bring up device |
---|
326 | echo "Setting $DEVICE with option:on|any and Variable IP= $IP: ipconfig -t ${ROUNDTTT} ${DEVICE} " |
---|
327 | ipconfig -t ${ROUNDTTT} ${DEVICE} |
---|
328 | ;; |
---|
329 | dhcp|bootp|rarp|both) |
---|
330 | echo "Setting $DEVICE with option:dhcp|bootp|rarp|both and Variable IP= $IP: ipconfig -t ${ROUNDTTT} -c ${IP} -d ${DEVICE} " |
---|
331 | ipconfig -t ${ROUNDTTT} -c ${IP} -d ${DEVICE} |
---|
332 | ;; |
---|
333 | *) |
---|
334 | echo "Setting $DEVICE with option * and Variable IP= $IP: ipconfig -t ${ROUNDTTT} -d $IP " |
---|
335 | ipconfig -t ${ROUNDTTT} -d $IP |
---|
336 | # grab device entry from ip option |
---|
337 | NEW_DEVICE=${IP#*:*:*:*:*:*} |
---|
338 | if [ "${NEW_DEVICE}" != "${IP}" ]; then |
---|
339 | NEW_DEVICE=${NEW_DEVICE%:*} |
---|
340 | else |
---|
341 | # wrong parse, possibly only a partial string |
---|
342 | NEW_DEVICE= |
---|
343 | fi |
---|
344 | if [ -n "${NEW_DEVICE}" ]; then |
---|
345 | DEVICE="${NEW_DEVICE}" |
---|
346 | fi |
---|
347 | ;; |
---|
348 | esac |
---|
349 | done |
---|
350 | |
---|
351 | # source ipconfig output |
---|
352 | if [ -n "${DEVICE}" ]; then |
---|
353 | . /tmp/net-${DEVICE}.conf |
---|
354 | DEVICECFG="/tmp/net-${DEVICE}" |
---|
355 | export DEVICECFG |
---|
356 | export DEVICE |
---|
357 | echo "DEVICE=$DEVICE" >> $CFGINITRD |
---|
358 | echo "DEVICECFG=$DEVICECFG" >> $CFGINITRD |
---|
359 | echo "exportando variable DEVICE con valor = $DEVICE y el DEVICECFG con valor $DEVICECFG" |
---|
360 | else |
---|
361 | # source any interface as not exaclty specified |
---|
362 | . /tmp/net-*.conf |
---|
363 | fi |
---|
364 | } |
---|
365 | |
---|
366 | ##################################################################### |
---|
367 | # Ask yesno question. |
---|
368 | # |
---|
369 | # Usage: yesno OPTIONS QUESTION |
---|
370 | # |
---|
371 | # Options: |
---|
372 | # --timeout N Timeout if no input seen in N seconds. |
---|
373 | # --default ANS Use ANS as the default answer on timeout or |
---|
374 | # if an empty answer is provided. |
---|
375 | # |
---|
376 | # Exit status is the answer. 0=yes 1=no |
---|
377 | |
---|
378 | ogYesNo() |
---|
379 | { |
---|
380 | local ans |
---|
381 | local ok=0 |
---|
382 | local timeout=0 |
---|
383 | local default |
---|
384 | local t |
---|
385 | |
---|
386 | while [[ "$1" ]] |
---|
387 | do |
---|
388 | case "$1" in |
---|
389 | --default) |
---|
390 | shift |
---|
391 | default=$1 |
---|
392 | if [[ ! "$default" ]]; then error "Missing default value"; fi |
---|
393 | t=$(echo $default | tr '[:upper:]' '[:lower:]') |
---|
394 | |
---|
395 | if [[ "$t" != 'y' && "$t" != 'yes' && "$t" != 'n' && "$t" != 'no' ]]; then |
---|
396 | error "Illegal default answer: $default" |
---|
397 | fi |
---|
398 | default=$t |
---|
399 | shift |
---|
400 | ;; |
---|
401 | |
---|
402 | --timeout) |
---|
403 | shift |
---|
404 | timeout=$1 |
---|
405 | if [[ ! "$timeout" ]]; then error "Missing timeout value"; fi |
---|
406 | #if [[ ! "$timeout" =~ ^[0-9][0-9]*$ ]]; then error "Illegal timeout value: $timeout"; fi |
---|
407 | shift |
---|
408 | ;; |
---|
409 | |
---|
410 | -*) |
---|
411 | error "Unrecognized option: $1" |
---|
412 | ;; |
---|
413 | |
---|
414 | *) |
---|
415 | break |
---|
416 | ;; |
---|
417 | esac |
---|
418 | done |
---|
419 | |
---|
420 | if [[ $timeout -ne 0 && ! "$default" ]]; then |
---|
421 | error "Non-zero timeout requires a default answer" |
---|
422 | fi |
---|
423 | |
---|
424 | if [[ ! "$*" ]]; then error "Missing question"; fi |
---|
425 | |
---|
426 | while [[ $ok -eq 0 ]] |
---|
427 | do |
---|
428 | if [[ $timeout -ne 0 ]]; then |
---|
429 | if ! read -t $timeout -p "$*" ans; then |
---|
430 | ans=$default |
---|
431 | else |
---|
432 | # Turn off timeout if answer entered. |
---|
433 | timeout=0 |
---|
434 | if [[ ! "$ans" ]]; then ans=$default; fi |
---|
435 | fi |
---|
436 | else |
---|
437 | read -p "$*" ans |
---|
438 | if [[ ! "$ans" ]]; then |
---|
439 | ans=$default |
---|
440 | else |
---|
441 | ans=$(echo $ans | tr '[:upper:]' '[:lower:]') |
---|
442 | fi |
---|
443 | fi |
---|
444 | |
---|
445 | if [[ "$ans" == 'y' || "$ans" == 'yes' || "$ans" == 'n' || "$ans" == 'no' ]]; then |
---|
446 | ok=1 |
---|
447 | fi |
---|
448 | |
---|
449 | if [[ $ok -eq 0 ]]; then warning "Valid answers are: yes y no n"; fi |
---|
450 | done |
---|
451 | [[ "$ans" = "y" || "$ans" == "yes" ]] |
---|
452 | } |
---|
453 | |
---|
454 | |
---|