Welcome unzip(1), a pure BSD drop-in replacement for ports/unzip. In its

current state, it can handle all but four of the 991 zip files (including
jar files) I was able to identify in the ports tree.  The remaining four
are two self-extracting archives and two which have garbage preceding the
first local header.  This limitation is a feature of libarchive(3) which
I am currently working to resolve.

The code is unnecessarily large due to the need to emulate the exact
command-line syntax and behaviour of ports/unzip.  My initial incompatible
implementation was one quarter the size of the one I am committing here.
This commit is contained in:
Dag-Erling Smørgrav 2008-01-08 08:00:06 +00:00
parent f029c53a5c
commit 497a8b25b5
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=175154
3 changed files with 920 additions and 0 deletions

9
usr.bin/unzip/Makefile Normal file
View File

@ -0,0 +1,9 @@
# $FreeBSD$
PROG = unzip
WARNS ?= 6
CSTD = c99
DPADD = ${LIBARCHIVE}
LDADD = -larchive
.include <bsd.prog.mk>

155
usr.bin/unzip/unzip.1 Normal file
View File

@ -0,0 +1,155 @@
.\"-
.\" Copyright (c) 2007-2008 Dag-Erling Coïdan Smørgrav
.\" All rights reserved.
.\"
.\" 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.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
.\"
.\" $FreeBSD$
.\"
.Dd January 8, 2008
.Dt UNZIP 1
.Os
.Sh NAME
.Nm unzip
.Nd extract files from a ZIP archive
.Sh SYNOPSIS
.Nm
.Op Fl ajLlnoqu
.Op Fl d Ar dir
.Ar zipfile
.Sh DESCRIPTION
.\" ...
.Pp
The following options are available:
.Bl -tag -width Fl
.It Fl a
When extracting a text file, convert DOS-style line endings to
Unix-style line endings.
.It Fl d Ar dir
Extract files into the specified directory rather than the current
directory.
.It Fl j
Ignore directories stored in the zipfile; instead, extract all files
directly into the extraction directory.
.It Fl L
Convert the names of the extracted files and directories to lowercase.
.It Fl l
List, rather than extract, the contents of the zipfile.
.It Fl n
No overwrite.
When extacting a file from the zipfile, if a file with the same name
already exists on disk, the file is silently skipped.
.It Fl o
Overwrite.
When extacting a file from the zipfile, if a file with the same name
already exists on disk, the existing file is replaced with the file
from the zipfile.
.It Fl q
Quiet: print less information while extracting.
.It Fl u
Update.
When extacting a file from the zipfile, if a file with the same name
already exists on disk, the existing file is replaced with the file
from the zipfile if and only if the latter is newer than the former.
Otherwise, the file is silently skipped.
.El
.Pp
Note that only one of
.Fl n ,
.Fl o
and
.Fl u
may be specified.
.Sh ENVIRONMENT
If the
.Ev UNZIP_DEBUG
environment variable is defined, the
.Fl q
command-line option has no effect, and additional debugging
information will be printed to
.Va stderr .
.Sh COMPATIBILITY
The
.Nm
utility aims to be sufficiently compatible with other implementations
to serve as a drop-in replacement in the context of the
.Xr ports 7
system.
No attempt has been made to replicate functionality which is not
required for that purpose.
.Pp
For compatibility reasons, command-line options will be recognized if
they are listed not only before but also after the name of the
zipfile.
.Pp
Normally, the
.Fl a
option should only affect files which are marked as text files in the
zipfile's central directory.
Since the
.Xr archive 3
library reads zipfiles sequentially, and does not use the central
directory, that information is not available to the
.Nm
utility.
Instead, the
.Nm
utility will assume that a file is a text file if no non-ASCII
characters are present within the first block of data decompressed for
that file.
If non-ASCII characters appear in subsequent blocks of data, a warning
will be issued.
.Pp
The
.Nm
utility is only able to process ZIP archives handled by
.Xr libarchive 3 .
Depending on the installed version of
.Xr libarchive ,
this may or may not include self-extracting archives.
.Sh BUGS
The
.Nm
utility currently does not support asking the user whether to
overwrite or skip a file that already exists on disk.
To be on the safe side, it will fail if it encounters a file that
already exists and neither the
.Fl n
nor the
.Fl o
command line option was specified.
.Sh SEE ALSO
.Xr libarchive 3
.Sh HISTORY
The
.Nm
utility appeared in
.Fx 8.0 .
.Sh AUTHORS
The
.Nm
utility and this manual page were written by
.An Dag-Erling Sm\(/orgrav Aq des@FreeBSD.org .
It uses the
.Xr archive 3
library developed by
.An Tim Kientzle Aq kientzle@FreeBSD.org .

756
usr.bin/unzip/unzip.c Normal file
View File

@ -0,0 +1,756 @@
/*-
* Copyright (c) 2007-2008 Dag-Erling Coïdan Smørgrav
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
* $FreeBSD$
*
* This file would be much shorter if we didn't care about command-line
* compatibility with Info-ZIP's UnZip, which requires us to duplicate
* parts of libarchive in order to gain more detailed control of its
* behaviour for the purpose of implementing the -n, -o, -L and -a
* options.
*/
#include <sys/queue.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <archive.h>
#include <archive_entry.h>
/* command-line options */
static int a_opt; /* convert EOL */
static const char *d_arg; /* directory */
static int j_opt; /* junk directories */
static int L_opt; /* lowercase names */
static int l_opt; /* list */
static int n_opt; /* never overwrite */
static int o_opt; /* always overwrite */
static int q_opt; /* quiet */
static int u_opt; /* update */
/* time when unzip started */
static time_t now;
/* debug flag */
static int unzip_debug;
/* running on tty? */
static int tty;
/* convenience macro */
/* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */
#define ac(call) \
do { \
int acret = (call); \
if (acret != ARCHIVE_OK) \
errorx("%s", archive_error_string(a)); \
} while (0)
/*
* Indicates that last info() did not end with EOL. This helps error() et
* al. avoid printing an error message on the same line as an incomplete
* informational message.
*/
static int noeol;
/* fatal error message + errno */
static void
error(const char *fmt, ...)
{
va_list ap;
if (noeol)
fprintf(stdout, "\n");
fflush(stdout);
fprintf(stderr, "unzip: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, ": %s\n", strerror(errno));
exit(1);
}
/* fatal error message, no errno */
static void
errorx(const char *fmt, ...)
{
va_list ap;
if (noeol)
fprintf(stdout, "\n");
fflush(stdout);
fprintf(stderr, "unzip: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
#if 0
/* non-fatal error message + errno */
static void
warning(const char *fmt, ...)
{
va_list ap;
if (noeol)
fprintf(stdout, "\n");
fflush(stdout);
fprintf(stderr, "unzip: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, ": %s\n", strerror(errno));
}
#endif
/* non-fatal error message, no errno */
static void
warningx(const char *fmt, ...)
{
va_list ap;
if (noeol)
fprintf(stdout, "\n");
fflush(stdout);
fprintf(stderr, "unzip: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
/* informational message (if not -q) */
static void
info(const char *fmt, ...)
{
va_list ap;
int i;
if (q_opt && !unzip_debug)
return;
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
fflush(stdout);
for (i = 0; fmt[i] != '\0'; ++i)
/* nothing */ ;
noeol = !(i && fmt[i - 1] == '\n');
}
/* debug message (if unzip_debug) */
static void
debug(const char *fmt, ...)
{
va_list ap;
int i;
if (!unzip_debug)
return;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fflush(stderr);
for (i = 0; fmt[i] != '\0'; ++i)
/* nothing */ ;
noeol = !(i && fmt[i - 1] == '\n');
}
/* duplicate a path name, possibly converting to lower case */
static char *
pathdup(const char *path)
{
char *str;
int len;
len = strlen(path);
while (len && path[len - 1] == '/')
len--;
if ((str = malloc(len + 1)) == NULL) {
errno = ENOMEM;
error("malloc()");
}
for (int i = 0; i < len; ++i)
str[i] = L_opt ? tolower(path[i]) : path[i];
str[len] = '\0';
return (str);
}
/* concatenate two path names */
static char *
pathcat(const char *prefix, const char *path)
{
char *str;
int prelen, len;
prelen = prefix ? strlen(prefix) + 1 : 0;
len = strlen(path) + 1;
if ((str = malloc(prelen + len)) == NULL) {
errno = ENOMEM;
error("malloc()");
}
if (prefix) {
memcpy(str, prefix, prelen); /* includes zero */
str[prelen - 1] = '/'; /* splat zero */
}
memcpy(str + prelen, path, len); /* includes zero */
return (str);
}
/*
* Pattern lists for include / exclude processing
*/
struct pattern {
STAILQ_ENTRY(pattern) link;
char pattern[];
};
STAILQ_HEAD(pattern_list, pattern);
static struct pattern_list include = STAILQ_HEAD_INITIALIZER(include);
static struct pattern_list exclude = STAILQ_HEAD_INITIALIZER(exclude);
/*
* Add an entry to a pattern list
*/
static void
add_pattern(struct pattern_list *list, const char *pattern)
{
struct pattern *entry;
int len;
debug("adding pattern '%s'\n", pattern);
len = strlen(pattern);
if ((entry = malloc(sizeof *entry + len + 1)) == NULL) {
errno = ENOMEM;
error("malloc()");
}
memset(&entry->link, 0, sizeof entry->link);
memcpy(entry->pattern, pattern, len + 1);
STAILQ_INSERT_TAIL(list, entry, link);
}
/*
* Match a string against a list of patterns
*/
static int
match_pattern(struct pattern_list *list, const char *str)
{
struct pattern *entry;
STAILQ_FOREACH(entry, list, link) {
if (fnmatch(entry->pattern, str, 0) == 0)
return (1);
}
return (0);
}
/*
* Verify that a given pathname is in the include list and not in the
* exclude list.
*/
static int
accept_pathname(const char *pathname)
{
if (!STAILQ_EMPTY(&include) && !match_pattern(&include, pathname))
return (0);
if (!STAILQ_EMPTY(&exclude) && match_pattern(&exclude, pathname))
return (0);
return (1);
}
/*
* Create the specified directory with the specified mode, taking certain
* precautions on they way.
*/
static void
make_dir(const char *path, int mode)
{
struct stat sb;
if (lstat(path, &sb) == 0) {
if (S_ISDIR(sb.st_mode))
return;
/*
* Normally, we should either ask the user about removing
* the non-directory of the same name as a directory we
* wish to create, or respect the -n or -o command-line
* options. However, this may lead to a later failure or
* even compromise (if this non-directory happens to be a
* symlink to somewhere unsafe), so we don't.
*/
/*
* Don't check unlink() result; failure will cause mkdir()
* to fail later, which we will catch.
*/
(void)unlink(path);
}
if (mkdir(path, mode) != 0 && errno != EEXIST)
error("mkdir('%s')", path);
}
/*
* Ensure that all directories leading up to (but not including) the
* specified path exist.
*
* XXX inefficient.
*/
static void
make_parent(char *path)
{
struct stat sb;
char *sep;
sep = strrchr(path, '/');
if (sep == NULL || sep == path)
return;
*sep = '\0';
if (lstat(path, &sb) == 0) {
if (S_ISDIR(sb.st_mode)) {
*sep = '/';
return;
}
unlink(path);
}
make_parent(path);
mkdir(path, 0755);
*sep = '/';
#if 0
for (sep = path; (sep = strchr(sep, '/')) != NULL; sep++) {
/* root in case of absolute d_arg */
if (sep == path)
continue;
*sep = '\0';
make_dir(path, 0755);
*sep = '/';
}
#endif
}
/*
* Extract a directory.
*/
static void
extract_dir(struct archive *a, struct archive_entry *e, const char *path)
{
int mode;
mode = archive_entry_filetype(e) & 0777;
if (mode == 0)
mode = 0755;
/*
* Some zipfiles contain directories with weird permissions such
* as 0644 or 0444. This can cause strange issues such as being
* unable to extract files into the directory we just created, or
* the user being unable to remove the directory later without
* first manually changing its permissions. Therefore, we whack
* the permissions into shape, assuming that the user wants full
* access and that anyone who gets read access also gets execute
* access.
*/
mode |= 0700;
if (mode & 0040)
mode |= 0010;
if (mode & 0004)
mode |= 0001;
info("d %s\n", path);
make_dir(path, mode);
ac(archive_read_data_skip(a));
}
static unsigned char buffer[8192];
static char spinner[] = { '|', '/', '-', '\\' };
/*
* Extract a regular file.
*/
static void
extract_file(struct archive *a, struct archive_entry *e, const char *path)
{
int mode;
time_t mtime;
struct stat sb;
struct timeval tv[2];
int cr, fd, text, warn;
ssize_t len;
unsigned char *p, *q, *end;
mode = archive_entry_filetype(e) & 0777;
if (mode == 0)
mode = 0644;
mtime = archive_entry_mtime(e);
/* look for existing file of same name */
if (lstat(path, &sb) == 0) {
if (u_opt) {
/* check if up-to-date */
if (S_ISREG(sb.st_mode) && sb.st_mtime > mtime)
return;
(void)unlink(path);
} else if (o_opt) {
/* overwrite */
(void)unlink(path);
} else if (n_opt) {
/* do not overwrite */
return;
} else {
/* XXX ask user */
errorx("not implemented");
}
}
if ((fd = open(path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0)
error("open('%s')", path);
/* loop over file contents and write to disk */
info("x %s", path);
text = a_opt;
warn = 0;
cr = 0;
for (int n = 0; ; n++) {
if (tty && (n % 4) == 0)
info(" %c\b\b", spinner[(n / 4) % sizeof spinner]);
len = archive_read_data(a, buffer, sizeof buffer);
if (len < 0)
ac(len);
/* left over CR from previous buffer */
if (a_opt && cr) {
if (len == 0 || buffer[0] != '\n')
if (write(fd, "\r", 1) != 1)
error("write('%s')", path);
cr = 0;
}
/* EOF */
if (len == 0)
break;
end = buffer + len;
/*
* Detect whether this is a text file. The correct way to
* do this is to check the least significant bit of the
* "internal file attributes" field of the corresponding
* file header in the central directory, but libarchive
* does not read the central directory, so we have to
* guess by looking for non-ASCII characters in the
* buffer. Hopefully we won't guess wrong. If we do
* guess wrong, we print a warning message later.
*/
if (a_opt && n == 0) {
for (p = buffer; p < end; ++p) {
if (!isascii((unsigned char)*p)) {
text = 0;
break;
}
}
}
/* simple case */
if (!a_opt || !text) {
if (write(fd, buffer, len) != len)
error("write('%s')", path);
continue;
}
/* hard case: convert \r\n to \n (sigh...) */
for (p = buffer; p < end; p = q + 1) {
for (q = p; q < end; q++) {
if (!warn && !isascii(*q)) {
warningx("%s may be corrupted due"
" to weak text file detection"
" heuristic", path);
warn = 1;
}
if (q[0] != '\r')
continue;
if (&q[1] == end) {
cr = 1;
break;
}
if (q[1] == '\n')
break;
}
if (write(fd, p, q - p) != q - p)
error("write('%s')", path);
}
}
if (tty)
info(" \b\b");
if (text)
info(" (text)");
info("\n");
/* set access and modification time */
tv[0].tv_sec = now;
tv[0].tv_usec = 0;
tv[1].tv_sec = mtime;
tv[1].tv_usec = 0;
if (futimes(fd, tv) != 0)
error("utimes('%s')", path);
if (close(fd) != 0)
error("close('%s')", path);
}
/*
* Extract a zipfile entry: first perform some sanity checks to ensure
* that it is either a directory or a regular file and that the path is
* not absolute and does not try to break out of the current directory;
* then call either extract_dir() or extract_file() as appropriate.
*
* This is complicated a bit by the various ways in which we need to
* manipulate the path name. Case conversion (if requested by the -L
* option) happens first, but the include / exclude patterns are applied
* to the full converted path name, before the directory part of the path
* is removed in accordance with the -j option. Sanity checks are
* intentionally done earlier than they need to be, so the user will get a
* warning about insecure paths even for files or directories which
* wouldn't be extracted anyway.
*/
static void
extract(struct archive *a, struct archive_entry *e)
{
char *pathname, *realpathname;
mode_t filetype;
char *p, *q;
pathname = pathdup(archive_entry_pathname(e));
filetype = archive_entry_filetype(e);
/* sanity checks */
if (pathname[0] == '/' ||
strncmp(pathname, "../", 3) == 0 ||
strstr(pathname, "/../") != NULL) {
warningx("skipping insecure entry '%s'", pathname);
ac(archive_read_data_skip(a));
free(pathname);
return;
}
/* I don't think this can happen in a zipfile.. */
if (!S_ISDIR(filetype) && !S_ISREG(filetype)) {
warningx("skipping non-regular entry '%s'", pathname);
ac(archive_read_data_skip(a));
free(pathname);
return;
}
/* skip directories in -j case */
if (S_ISDIR(filetype) && j_opt) {
ac(archive_read_data_skip(a));
free(pathname);
return;
}
/* apply include / exclude patterns */
if (!accept_pathname(pathname)) {
ac(archive_read_data_skip(a));
free(pathname);
return;
}
/* apply -j and -d */
if (j_opt) {
for (p = q = pathname; *p; ++p)
if (*p == '/')
q = p + 1;
realpathname = pathcat(d_arg, q);
} else {
realpathname = pathcat(d_arg, pathname);
}
/* ensure that parent directory exists */
make_parent(realpathname);
if (S_ISDIR(filetype))
extract_dir(a, e, realpathname);
else
extract_file(a, e, realpathname);
free(realpathname);
free(pathname);
}
/*
* Print the name of an entry to stdout.
*/
static void
list(struct archive *a, struct archive_entry *e)
{
printf("%s\n", archive_entry_pathname(e));
ac(archive_read_data_skip(a));
}
/*
* Main loop: open the zipfile, iterate over its contents and decide what
* to do with each entry.
*/
static void
unzip(const char *fn)
{
struct archive *a;
struct archive_entry *e;
int fd, ret;
if ((fd = open(fn, O_RDONLY)) < 0)
error("%s", fn);
a = archive_read_new();
ac(archive_read_support_format_zip(a));
ac(archive_read_open_fd(a, fd, 8192));
for (;;) {
ret = archive_read_next_header(a, &e);
if (ret == ARCHIVE_EOF)
break;
ac(ret);
if (l_opt)
list(a, e);
else
extract(a, e);
}
ac(archive_read_close(a));
(void)archive_read_finish(a);
if (close(fd) != 0)
error("%s", fn);
}
static void
usage(void)
{
fprintf(stderr, "usage: unzip [-ajLlnoqu] [-d dir] zipfile\n");
exit(1);
}
static int
getopts(int argc, char *argv[])
{
int opt;
optreset = optind = 1;
while ((opt = getopt(argc, argv, "ad:jLlnoqux:")) != -1)
switch (opt) {
case 'a':
a_opt = 1;
break;
case 'd':
d_arg = optarg;
break;
case 'j':
j_opt = 1;
break;
case 'L':
L_opt = 1;
break;
case 'l':
l_opt = 1;
break;
case 'n':
n_opt = 1;
break;
case 'o':
o_opt = 1;
break;
case 'q':
q_opt = 1;
break;
case 'u':
u_opt = 1;
break;
case 'x':
add_pattern(&exclude, optarg);
break;
default:
usage();
}
return (optind);
}
int
main(int argc, char *argv[])
{
const char *zipfile;
int nopts;
if (isatty(STDOUT_FILENO))
tty = 1;
if (getenv("UNZIP_DEBUG") != NULL)
unzip_debug = 1;
for (int i = 0; i < argc; ++i)
debug("%s%c", argv[i], (i < argc - 1) ? ' ' : '\n');
/*
* Info-ZIP's unzip(1) expects certain options to come before the
* zipfile name, and others to come after - though it does not
* enforce this. For simplicity, we accept *all* options both
* before and after the zipfile name.
*/
nopts = getopts(argc, argv);
if (argc <= nopts)
usage();
zipfile = argv[nopts++];
while (nopts < argc && *argv[nopts] != '-')
add_pattern(&include, argv[nopts++]);
nopts--; /* fake argv[0] */
nopts += getopts(argc - nopts, argv + nopts);
if (n_opt + o_opt + u_opt > 1)
errorx("-n, -o and -u are contradictory");
time(&now);
unzip(zipfile);
exit(0);
}