]> git.ipfire.org Git - thirdparty/util-linux.git/blame - sys-utils/umount.c
libsmartcols: add debug messages
[thirdparty/util-linux.git] / sys-utils / umount.c
CommitLineData
db216e68
KZ
1/*
2 * umount(8) -- mount a filesystem
3 *
4 * Copyright (C) 2011 Red Hat, Inc. All rights reserved.
5 * Written by Karel Zak <kzak@redhat.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
7cebf0bb
SK
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
db216e68
KZ
20 */
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <errno.h>
25#include <string.h>
26#include <getopt.h>
27#include <unistd.h>
28#include <sys/types.h>
29
30#include <libmount.h>
31
32#include "nls.h"
33#include "c.h"
34#include "env.h"
35#include "optutils.h"
73f9a114 36#include "exitcodes.h"
efb8854f 37#include "closestream.h"
13ee1c91 38#include "pathnames.h"
cc8cc8f3 39#include "canonicalize.h"
4eb49f63 40#include "xalloc.h"
db216e68
KZ
41
42static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)),
43 const char *filename, int line)
44{
45 if (filename)
46 warnx(_("%s: parse error: ignore entry at line %d."),
47 filename, line);
48 return 0;
49}
50
ac8ecab4 51
db216e68
KZ
52static void __attribute__((__noreturn__)) print_version(void)
53{
54 const char *ver = NULL;
ac8ecab4 55 const char **features = NULL, **p;
db216e68
KZ
56
57 mnt_get_library_version(&ver);
ac8ecab4 58 mnt_get_library_features(&features);
db216e68 59
ac8ecab4
KZ
60 printf(_("%s from %s (libmount %s"),
61 program_invocation_short_name,
62 PACKAGE_STRING,
63 ver);
64 p = features;
65 while (p && *p) {
66 fputs(p == features ? ": " : ", ", stdout);
67 fputs(*p++, stdout);
68 }
69 fputs(")\n", stdout);
73f9a114 70 exit(MOUNT_EX_SUCCESS);
db216e68 71}
db216e68
KZ
72static void __attribute__((__noreturn__)) usage(FILE *out)
73{
74 fputs(USAGE_HEADER, out);
75 fprintf(out, _(
76 " %1$s [-hV]\n"
77 " %1$s -a [options]\n"
78 " %1$s [options] <source> | <directory>\n"),
79 program_invocation_short_name);
80
81 fputs(USAGE_OPTIONS, out);
4eb49f63 82 fputs(_(" -a, --all unmount all filesystems\n"), out);
8356d27d 83 fputs(_(" -A, --all-targets unmount all mountpoints for the given device in the\n"
4ce393f4 84 " current namespace\n"), out);
83d91100
SK
85 fputs(_(" -c, --no-canonicalize don't canonicalize paths\n"), out);
86 fputs(_(" -d, --detach-loop if mounted loop device, also free this loop device\n"), out);
87 fputs(_(" --fake dry run; skip the umount(2) syscall\n"), out);
88 fputs(_(" -f, --force force unmount (in case of an unreachable NFS system)\n"), out);
89 fputs(_(" -i, --internal-only don't call the umount.<type> helpers\n"), out);
90 fputs(_(" -n, --no-mtab don't write to /etc/mtab\n"), out);
4ce393f4 91 fputs(_(" -l, --lazy detach the filesystem now, clean up things later\n"), out);
83d91100
SK
92 fputs(_(" -O, --test-opts <list> limit the set of filesystems (use with -a)\n"), out);
93 fputs(_(" -R, --recursive recursively unmount a target with all its children\n"), out);
4ce393f4 94 fputs(_(" -r, --read-only in case unmounting fails, try to remount read-only\n"), out);
83d91100
SK
95 fputs(_(" -t, --types <list> limit the set of filesystem types\n"), out);
96 fputs(_(" -v, --verbose say what is being done\n"), out);
db216e68
KZ
97
98 fputs(USAGE_SEPARATOR, out);
99 fputs(USAGE_HELP, out);
100 fputs(USAGE_VERSION, out);
101 fprintf(out, USAGE_MAN_TAIL("umount(8)"));
102
73f9a114 103 exit(out == stderr ? MOUNT_EX_USAGE : MOUNT_EX_SUCCESS);
db216e68
KZ
104}
105
106static void __attribute__((__noreturn__)) exit_non_root(const char *option)
107{
108 const uid_t ruid = getuid();
109 const uid_t euid = geteuid();
110
111 if (ruid == 0 && euid != 0) {
112 /* user is root, but setuid to non-root */
113 if (option)
73f9a114 114 errx(MOUNT_EX_USAGE,
db216e68
KZ
115 _("only root can use \"--%s\" option "
116 "(effective UID is %u)"),
117 option, euid);
73f9a114 118 errx(MOUNT_EX_USAGE, _("only root can do that "
db216e68
KZ
119 "(effective UID is %u)"), euid);
120 }
121 if (option)
73f9a114
KZ
122 errx(MOUNT_EX_USAGE, _("only root can use \"--%s\" option"), option);
123 errx(MOUNT_EX_USAGE, _("only root can do that"));
124}
125
84600ddc
KZ
126static void success_message(struct libmnt_context *cxt)
127{
128 const char *tgt, *src;
129
130 if (mnt_context_helper_executed(cxt)
131 || mnt_context_get_status(cxt) != 1)
132 return;
133
134 tgt = mnt_context_get_target(cxt);
135 if (!tgt)
136 return;
137
138 src = mnt_context_get_source(cxt);
139 if (src)
140 warnx(_("%s (%s) unmounted"), tgt, src);
141 else
142 warnx(_("%s unmounted"), tgt);
143}
144
73f9a114
KZ
145/*
146 * Handles generic errors like ENOMEM, ...
147 *
148 * rc = 0 success
149 * <0 error (usually -errno)
150 *
151 * Returns exit status (MOUNT_EX_*) and prints error message.
152 */
153static int handle_generic_errors(int rc, const char *msg, ...)
154{
155 va_list va;
156
157 va_start(va, msg);
158 errno = -rc;
159
160 switch(errno) {
161 case EINVAL:
162 case EPERM:
163 vwarn(msg, va);
164 rc = MOUNT_EX_USAGE;
165 break;
166 case ENOMEM:
167 vwarn(msg, va);
168 rc = MOUNT_EX_SYSERR;
169 break;
170 default:
171 vwarn(msg, va);
172 rc = MOUNT_EX_FAIL;
173 break;
174 }
175 va_end(va);
176 return rc;
177}
178
179static int mk_exit_code(struct libmnt_context *cxt, int rc)
180{
181 int syserr;
182 const char *tgt = mnt_context_get_target(cxt);
183
184 if (mnt_context_helper_executed(cxt))
185 /*
186 * /sbin/umount.<type> called, return status
187 */
188 return mnt_context_get_helper_status(cxt);
189
190 if (rc == 0 && mnt_context_get_status(cxt) == 1)
191 /*
192 * Libmount success && syscall success.
193 */
194 return MOUNT_EX_SUCCESS;
195
196
197 if (!mnt_context_syscall_called(cxt)) {
198 /*
199 * libmount errors (extra library checks)
200 */
726f9fbf
KZ
201 if (rc == -EPERM && !mnt_context_tab_applied(cxt)) {
202 /* failed to evaluate permissions because not found
203 * relevant entry in mtab */
204 warnx(_("%s: not mounted"), tgt);
205 return MOUNT_EX_USAGE;
206 }
73f9a114
KZ
207 return handle_generic_errors(rc, _("%s: umount failed"), tgt);
208
209 } else if (mnt_context_get_syscall_errno(cxt) == 0) {
210 /*
211 * umount(2) syscall success, but something else failed
212 * (probably error in mtab processing).
213 */
214 if (rc < 0)
215 return handle_generic_errors(rc,
4ce393f4 216 _("%s: filesystem was unmounted, but mount(8) failed"),
73f9a114
KZ
217 tgt);
218
219 return MOUNT_EX_SOFTWARE; /* internal error */
220
221 }
222
223 /*
224 * umount(2) errors
225 */
226 syserr = mnt_context_get_syscall_errno(cxt);
227
228 switch(syserr) {
229 case ENXIO:
230 warnx(_("%s: invalid block device"), tgt); /* ??? */
231 break;
232 case EINVAL:
233 warnx(_("%s: not mounted"), tgt);
234 break;
235 case EIO:
236 warnx(_("%s: can't write superblock"), tgt);
237 break;
238 case EBUSY:
4ce393f4
BS
239 warnx(_("%s: target is busy\n"
240 " (In some cases useful info about processes that\n"
241 " use the device is found by lsof(8) or fuser(1).)"),
73f9a114 242 tgt);
7e1b1446 243 break;
73f9a114 244 case ENOENT:
7ba207e7
KZ
245 if (tgt && *tgt)
246 warnx(_("%s: mountpoint not found"), tgt);
247 else
248 warnx(_("undefined mountpoint"));
73f9a114
KZ
249 break;
250 case EPERM:
4ce393f4 251 warnx(_("%s: must be superuser to unmount"), tgt);
73f9a114
KZ
252 break;
253 case EACCES:
4ce393f4 254 warnx(_("%s: block devices are not permitted on filesystem"), tgt);
73f9a114
KZ
255 break;
256 default:
257 errno = syserr;
4ce393f4 258 warn("%s", tgt);
73f9a114
KZ
259 break;
260 }
261 return MOUNT_EX_FAIL;
db216e68
KZ
262}
263
190c342a 264static int umount_all(struct libmnt_context *cxt)
db216e68 265{
190c342a
KZ
266 struct libmnt_iter *itr;
267 struct libmnt_fs *fs;
268 int mntrc, ignored, rc = 0;
269
270 itr = mnt_new_iter(MNT_ITER_BACKWARD);
271 if (!itr) {
272 warn(_("failed to initialize libmount iterator"));
06069d5f 273 return MOUNT_EX_SYSERR;
190c342a
KZ
274 }
275
276 while (mnt_context_next_umount(cxt, itr, &fs, &mntrc, &ignored) == 0) {
277
278 const char *tgt = mnt_fs_get_target(fs);
279
280 if (ignored) {
281 if (mnt_context_is_verbose(cxt))
282 printf(_("%-25s: ignored\n"), tgt);
190c342a 283 } else {
0ce2fe87 284 int xrc = mk_exit_code(cxt, mntrc);
73f9a114 285
0ce2fe87
KZ
286 if (xrc == MOUNT_EX_SUCCESS
287 && mnt_context_is_verbose(cxt))
4ce393f4 288 printf("%-25s: successfully unmounted\n", tgt);
0ce2fe87 289 rc |= xrc;
190c342a
KZ
290 }
291 }
292
0f2d6476 293 mnt_free_iter(itr);
190c342a 294 return rc;
db216e68
KZ
295}
296
297static int umount_one(struct libmnt_context *cxt, const char *spec)
298{
299 int rc;
300
301 if (!spec)
06069d5f 302 return MOUNT_EX_SOFTWARE;
db216e68
KZ
303
304 if (mnt_context_set_target(cxt, spec))
73f9a114 305 err(MOUNT_EX_SYSERR, _("failed to set umount target"));
db216e68
KZ
306
307 rc = mnt_context_umount(cxt);
73f9a114 308 rc = mk_exit_code(cxt, rc);
db216e68 309
84600ddc
KZ
310 if (rc == MOUNT_EX_SUCCESS && mnt_context_is_verbose(cxt))
311 success_message(cxt);
312
db216e68
KZ
313 mnt_reset_context(cxt);
314 return rc;
315}
316
4eb49f63
KZ
317static struct libmnt_table *new_mountinfo(struct libmnt_context *cxt)
318{
319 struct libmnt_table *tb = mnt_new_table();
320 if (!tb)
321 err(MOUNT_EX_SYSERR, _("libmount table allocation failed"));
322
323 mnt_table_set_parser_errcb(tb, table_parser_errcb);
324 mnt_table_set_cache(tb, mnt_context_get_cache(cxt));
325
326 if (mnt_table_parse_file(tb, _PATH_PROC_MOUNTINFO)) {
327 warn(_("failed to parse %s"), _PATH_PROC_MOUNTINFO);
50fccba1 328 mnt_unref_table(tb);
4eb49f63
KZ
329 tb = NULL;
330 }
331
332 return tb;
333}
334
335/*
336 * like umount_one() but does not return error is @spec not mounted
337 */
338static int umount_one_if_mounted(struct libmnt_context *cxt, const char *spec)
339{
340 int rc;
341 struct libmnt_fs *fs;
342
343 rc = mnt_context_find_umount_fs(cxt, spec, &fs);
344 if (rc == 1) {
345 rc = MOUNT_EX_SUCCESS; /* alredy unmounted */
346 mnt_reset_context(cxt);
347 } else if (rc < 0) {
348 rc = mk_exit_code(cxt, rc); /* error */
349 mnt_reset_context(cxt);
350 } else
351 rc = umount_one(cxt, mnt_fs_get_target(fs));
352
353 return rc;
354}
355
13ee1c91 356static int umount_do_recurse(struct libmnt_context *cxt,
4eb49f63 357 struct libmnt_table *tb, struct libmnt_fs *fs)
13ee1c91 358{
13ee1c91 359 struct libmnt_fs *child;
13ee1c91 360 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
4eb49f63 361 int rc;
13ee1c91
DR
362
363 if (!itr)
364 err(MOUNT_EX_SYSERR, _("libmount iterator allocation failed"));
4eb49f63
KZ
365
366 /* umount all childern */
13ee1c91 367 for (;;) {
4eb49f63 368 rc = mnt_table_next_child_fs(tb, itr, fs, &child);
13ee1c91 369 if (rc < 0) {
4eb49f63
KZ
370 warnx(_("failed to get child fs of %s"),
371 mnt_fs_get_target(fs));
13ee1c91
DR
372 rc = MOUNT_EX_SOFTWARE;
373 goto done;
374 } else if (rc == 1)
375 break; /* no more children */
376
377 rc = umount_do_recurse(cxt, tb, child);
378 if (rc != MOUNT_EX_SUCCESS)
379 goto done;
380 }
381
4eb49f63 382 rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs));
13ee1c91
DR
383done:
384 mnt_free_iter(itr);
385 return rc;
386}
387
388static int umount_recursive(struct libmnt_context *cxt, const char *spec)
389{
390 struct libmnt_table *tb;
4eb49f63 391 struct libmnt_fs *fs;
13ee1c91
DR
392 int rc;
393
4eb49f63
KZ
394 tb = new_mountinfo(cxt);
395 if (!tb)
396 return MOUNT_EX_SOFTWARE;
397
ae978c4d
KZ
398 /* it's always real mountpoint, don't assume that the target maybe a device */
399 mnt_context_disable_swapmatch(cxt, 1);
400
4eb49f63
KZ
401 fs = mnt_table_find_target(tb, spec, MNT_ITER_BACKWARD);
402 if (fs)
403 rc = umount_do_recurse(cxt, tb, fs);
404 else {
405 rc = MOUNT_EX_USAGE;
406 warnx(access(spec, F_OK) == 0 ?
407 _("%s: not mounted") :
408 _("%s: not found"), spec);
409 }
a8cc72de 410
50fccba1 411 mnt_unref_table(tb);
4eb49f63
KZ
412 return rc;
413}
7b4a2697 414
4eb49f63
KZ
415static int umount_alltargets(struct libmnt_context *cxt, const char *spec, int rec)
416{
417 struct libmnt_fs *fs;
418 struct libmnt_table *tb;
419 struct libmnt_iter *itr = NULL;
f697d61b 420 dev_t devno = 0;
4eb49f63
KZ
421 int rc;
422
423 /* Convert @spec to device name, Use the same logic like regular
424 * "umount <spec>".
13ee1c91 425 */
4eb49f63
KZ
426 rc = mnt_context_find_umount_fs(cxt, spec, &fs);
427 if (rc == 1) {
428 rc = MOUNT_EX_USAGE;
429 warnx(access(spec, F_OK) == 0 ?
430 _("%s: not mounted") :
431 _("%s: not found"), spec);
432 return rc;
433 }
434 if (rc < 0)
435 return mk_exit_code(cxt, rc); /* error */
13ee1c91 436
f697d61b 437 if (!mnt_fs_get_srcpath(fs) || !mnt_fs_get_devno(fs))
fd7c4924
KZ
438 errx(MOUNT_EX_USAGE, _("%s: failed to determine source "
439 "(--all-targets is unsupported on systems with "
440 "regular mtab file)."), spec);
4eb49f63
KZ
441
442 itr = mnt_new_iter(MNT_ITER_BACKWARD);
443 if (!itr)
444 err(MOUNT_EX_SYSERR, _("libmount iterator allocation failed"));
445
446 /* get on @cxt independent mountinfo */
447 tb = new_mountinfo(cxt);
448 if (!tb)
449 return MOUNT_EX_SOFTWARE;
450
451 /* Note that @fs is from mount context and the context will be reseted
452 * after each umount() call */
f697d61b 453 devno = mnt_fs_get_devno(fs);
4eb49f63
KZ
454 fs = NULL;
455
456 mnt_reset_context(cxt);
457
458 while (mnt_table_next_fs(tb, itr, &fs) == 0) {
f697d61b 459 if (mnt_fs_get_devno(fs) != devno)
4eb49f63
KZ
460 continue;
461 mnt_context_disable_swapmatch(cxt, 1);
462 if (rec)
13ee1c91 463 rc = umount_do_recurse(cxt, tb, fs);
4eb49f63
KZ
464 else
465 rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs));
466
467 if (rc != MOUNT_EX_SUCCESS)
468 break;
13ee1c91
DR
469 }
470
4eb49f63 471 mnt_free_iter(itr);
50fccba1 472 mnt_unref_table(tb);
4eb49f63 473
13ee1c91
DR
474 return rc;
475}
476
cc8cc8f3
KZ
477/*
478 * Check path -- non-root user should not be able to resolve path which is
479 * unreadable for him.
480 */
481static char *sanitize_path(const char *path)
482{
483 char *p;
484
485 if (!path)
486 return NULL;
487
488 p = canonicalize_path_restricted(path);
489 if (!p)
490 err(MOUNT_EX_USAGE, "%s", path);
491
492 return p;
493}
494
db216e68
KZ
495int main(int argc, char **argv)
496{
4eb49f63 497 int c, rc = 0, all = 0, recursive = 0, alltargets = 0;
db216e68
KZ
498 struct libmnt_context *cxt;
499 char *types = NULL;
500
501 enum {
502 UMOUNT_OPT_FAKE = CHAR_MAX + 1,
503 };
504
505 static const struct option longopts[] = {
506 { "all", 0, 0, 'a' },
4eb49f63 507 { "all-targets", 0, 0, 'A' },
db216e68
KZ
508 { "detach-loop", 0, 0, 'd' },
509 { "fake", 0, 0, UMOUNT_OPT_FAKE },
510 { "force", 0, 0, 'f' },
511 { "help", 0, 0, 'h' },
512 { "internal-only", 0, 0, 'i' },
513 { "lazy", 0, 0, 'l' },
514 { "no-canonicalize", 0, 0, 'c' },
515 { "no-mtab", 0, 0, 'n' },
516 { "read-only", 0, 0, 'r' },
13ee1c91 517 { "recursive", 0, 0, 'R' },
db216e68
KZ
518 { "test-opts", 1, 0, 'O' },
519 { "types", 1, 0, 't' },
520 { "verbose", 0, 0, 'v' },
521 { "version", 0, 0, 'V' },
522 { NULL, 0, 0, 0 }
523 };
524
a8cc72de 525 static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */
4eb49f63 526 { 'A','a' }, /* all-targets,all */
a8cc72de
KZ
527 { 'R','a' }, /* recursive,all */
528 { 'O','R','t'}, /* options,recursive,types */
529 { 'R','r' }, /* recursive,read-only */
530 { 0 }
531 };
532 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
533
db216e68
KZ
534 sanitize_env();
535 setlocale(LC_ALL, "");
536 bindtextdomain(PACKAGE, LOCALEDIR);
537 textdomain(PACKAGE);
efb8854f 538 atexit(close_stdout);
db216e68
KZ
539
540 mnt_init_debug(0);
541 cxt = mnt_new_context();
542 if (!cxt)
73f9a114 543 err(MOUNT_EX_SYSERR, _("libmount context allocation failed"));
db216e68
KZ
544
545 mnt_context_set_tables_errcb(cxt, table_parser_errcb);
546
4eb49f63 547 while ((c = getopt_long(argc, argv, "aAcdfhilnRrO:t:vV",
db216e68
KZ
548 longopts, NULL)) != -1) {
549
550
551 /* only few options are allowed for non-root users */
552 if (mnt_context_is_restricted(cxt) && !strchr("hdilVv", c))
553 exit_non_root(option_to_longopt(c, longopts));
554
a8cc72de
KZ
555 err_exclusive_options(c, longopts, excl, excl_st);
556
db216e68
KZ
557 switch(c) {
558 case 'a':
559 all = 1;
560 break;
4eb49f63
KZ
561 case 'A':
562 alltargets = 1;
563 break;
db216e68
KZ
564 case 'c':
565 mnt_context_disable_canonicalize(cxt, TRUE);
566 break;
567 case 'd':
568 mnt_context_enable_loopdel(cxt, TRUE);
569 break;
570 case UMOUNT_OPT_FAKE:
571 mnt_context_enable_fake(cxt, TRUE);
572 break;
573 case 'f':
574 mnt_context_enable_force(cxt, TRUE);
575 break;
576 case 'h':
577 usage(stdout);
578 break;
579 case 'i':
580 mnt_context_disable_helpers(cxt, TRUE);
581 break;
582 case 'l':
583 mnt_context_enable_lazy(cxt, TRUE);
584 break;
585 case 'n':
586 mnt_context_disable_mtab(cxt, TRUE);
587 break;
588 case 'r':
589 mnt_context_enable_rdonly_umount(cxt, TRUE);
590 break;
13ee1c91
DR
591 case 'R':
592 recursive = TRUE;
593 break;
db216e68
KZ
594 case 'O':
595 if (mnt_context_set_options_pattern(cxt, optarg))
73f9a114 596 err(MOUNT_EX_SYSERR, _("failed to set options pattern"));
db216e68
KZ
597 break;
598 case 't':
599 types = optarg;
600 break;
601 case 'v':
602 mnt_context_enable_verbose(cxt, TRUE);
603 break;
604 case 'V':
605 print_version();
606 break;
607 default:
608 usage(stderr);
609 break;
610 }
611 }
612
613 argc -= optind;
614 argv += optind;
615
616 if (all) {
617 if (!types)
618 types = "noproc,nodevfs,nodevpts,nosysfs,norpc_pipefs,nonfsd";
619
620 mnt_context_set_fstype_pattern(cxt, types);
621 rc = umount_all(cxt);
622
623 } else if (argc < 1) {
624 usage(stderr);
625
4eb49f63
KZ
626 } else if (alltargets) {
627 while (argc--)
628 rc += umount_alltargets(cxt, *argv++, recursive);
13ee1c91
DR
629 } else if (recursive) {
630 while (argc--)
631 rc += umount_recursive(cxt, *argv++);
632 } else {
cc8cc8f3 633 while (argc--) {
d41acf74 634 char *path = *argv;
cc8cc8f3 635
d41acf74
KZ
636 if (mnt_context_is_restricted(cxt)
637 && !mnt_tag_is_valid(path))
cc8cc8f3
KZ
638 path = sanitize_path(path);
639
640 rc += umount_one(cxt, path);
641
d41acf74 642 if (path != *argv)
cc8cc8f3 643 free(path);
d41acf74 644 argv++;
cc8cc8f3 645 }
13ee1c91 646 }
db216e68
KZ
647
648 mnt_free_context(cxt);
73f9a114 649 return rc;
db216e68
KZ
650}
651