This makes tar work perfectly, and adds in "--exclude" as an option

-Erik
This commit is contained in:
Erik Andersen 2000-04-09 15:17:40 +00:00
parent 84e09e4437
commit 0817d1372c
6 changed files with 247 additions and 177 deletions

View File

@ -11,16 +11,15 @@
patch your kernel with the devps patch in the kernel-patches/
directory.
* Wrote basename, killall, and uptime.
* tar has been completely rewritten by me. Both tar creation and
extraction are now well behaved. Costs 7.6k with all optional
tar features enabled, and 5k for just tar extraction support.
* Added freeramdisk, which will free up all memory associated
with a ram disk. Contributed by Emanuele Caratti <wiz@iol.it>
and then adjusted a bit by me.
* Added tr and dirname from John Lombardo <john@deltanet.com>
* Added echo and test (from me).
* Added usleep contributed by Nicolas Pitre <nico@cam.org>
* tar wouldn't create directory entries that don't end in '/',
now it does (thanks to Avery Pennarun <apenwarr@worldvisions.ca>)
* tar has been completely rewritten, and tar creation and extraction
is now much better behaved.
* Several fixes from Pavel Roskin <pavel_roskin@geocities.com>:
- When `tail' fails to open a file it now exits.
- When `syslogd' is given the `-n' option it should still use

View File

@ -25,7 +25,7 @@ BUILDTIME := $(shell TZ=UTC date --utc "+%Y.%m.%d-%H:%M%z")
# Set the following to `true' to make a debuggable build.
# Leave this set to `false' for production use.
# eg: `make DODEBUG=true tests'
DODEBUG = true
DODEBUG = false
# If you want a static binary, turn this on. I can't think
# of many situations where anybody would ever want it static,
@ -72,7 +72,7 @@ endif
# -D_GNU_SOURCE is needed because environ is used in init.c
ifeq ($(DODEBUG),true)
CFLAGS += -Wall -g -D_GNU_SOURCE
LDFLAGS =
LDFLAGS =
STRIP =
else
CFLAGS += -Wall $(OPTIMIZATION) -fomit-frame-pointer -fno-builtin -D_GNU_SOURCE

8
TODO
View File

@ -22,7 +22,7 @@ around to it some time. If you have any good ideas, please let me know.
* hwclock
* stty
* cut
* expr (maybe?) (ash builtin?)
* expr
@ -42,12 +42,6 @@ Perhaps I need to add a better build system (like the Linux kernel?)
-----------------------
There is no exclude file(s) option to tar. LRP's packaging system can not
function without this. Will you have the time to add this soon?
-----------------------
Feature request:
/bin/busybox --install -s which makes all links to commands that it

View File

@ -52,33 +52,33 @@
#include <sys/param.h> /* for PATH_MAX */
static const char tar_usage[] =
#ifdef BB_FEATURE_TAR_CREATE
static const char tar_usage[] =
"tar -[cxtvOf] [tarFile] [-X excludeFile] [FILE] ...\n\n"
"Create, extract, or list files from a tar file. Note that\n"
"this version of tar packs hard links as separate files.\n\n"
"Options:\n"
"\tc=create, x=extract, t=list contents, v=verbose,\n"
"\tO=extract to stdout, f=tarfile or \"-\" for stdin\n"
"\tX=exclude file\n";
"tar -[cxtvO] "
#else
static const char tar_usage[] =
"tar -[xtvO] [-f tarFile] [-X excludeFile] [FILE] ...\n\n"
"Extract, or list files stored in a tar file. This\n"
"version of tar does not support creation of tar files.\n\n"
"Options:\n"
"\tx=extract, t=list contents, v=verbose,\n"
"\tO=extract to stdout, f=tarfile or \"-\" for stdin\n"
"\tX=exclude file\n";
"tar -[xtvO] "
#endif
#if defined BB_FEATURE_TAR_EXCLUDE
"[--exclude File] "
#endif
"[-f tarFile] [FILE] ...\n\n"
"Create, extract, or list files from a tar file. Note that\n"
"this version of tar treats hard links as separate files.\n\n"
"Main operation mode:\n"
#ifdef BB_FEATURE_TAR_CREATE
"\tc\t\tcreate\n"
#endif
"\tx\t\textract\n"
"\tt\t\tlist\n"
"File selection:\n"
"\tf\t\tname of tarfile or \"-\" for stdin\n"
"\tO\t\textract to stdout\n"
#if defined BB_FEATURE_TAR_EXCLUDE
"\t--exclude\tfile to exclude\n"
#endif
"Informative output:\n"
"\tv\t\tverbosely list files processed\n"
;
/* Tar file constants */
#ifndef MAJOR
@ -91,31 +91,30 @@ static const char tar_usage[] =
struct TarHeader
{
/* byte offset */
char name[100]; /* 0 */
char mode[8]; /* 100 */
char uid[8]; /* 108 */
char gid[8]; /* 116 */
char size[12]; /* 124 */
char mtime[12]; /* 136 */
char chksum[8]; /* 148 */
char typeflag; /* 156 */
char linkname[100]; /* 157 */
char magic[6]; /* 257 */
char version[2]; /* 263 */
char uname[32]; /* 265 */
char gname[32]; /* 297 */
char devmajor[8]; /* 329 */
char devminor[8]; /* 337 */
char prefix[155]; /* 345 */
/* padding 500 */
char name[100]; /* 0-99 */
char mode[8]; /* 100-107 */
char uid[8]; /* 108-115 */
char gid[8]; /* 116-123 */
char size[12]; /* 124-135 */
char mtime[12]; /* 136-147 */
char chksum[8]; /* 148-155 */
char typeflag; /* 156-156 */
char linkname[100]; /* 157-256 */
char magic[6]; /* 257-262 */
char version[2]; /* 263-264 */
char uname[32]; /* 265-296 */
char gname[32]; /* 297-328 */
char devmajor[8]; /* 329-336 */
char devminor[8]; /* 337-344 */
char prefix[155]; /* 345-499 */
char padding[12]; /* 500-512 (pad to exactly the TAR_BLOCK_SIZE) */
};
typedef struct TarHeader TarHeader;
/* A few useful constants */
#define TAR_MAGIC "ustar" /* ustar and a null */
//#define TAR_VERSION "00" /* 00 and no null */
#define TAR_VERSION " " /* Be compatable with old GNU format */
#define TAR_VERSION " " /* Be compatable with GNU tar format */
#define TAR_MAGIC_LEN 6
#define TAR_VERSION_LEN 2
#define TAR_BLOCK_SIZE 512
@ -170,7 +169,9 @@ static int writeTarFile(const char* tarName, int tostdoutFlag,
extern int tar_main(int argc, char **argv)
{
char** excludeList=NULL;
#if defined BB_FEATURE_TAR_EXCLUDE
int excludeListSize=0;
#endif
const char *tarName=NULL;
int listFlag = FALSE;
int extractFlag = FALSE;
@ -224,22 +225,26 @@ extern int tar_main(int argc, char **argv)
tostdoutFlag = TRUE;
tarName = "-";
break;
case 'X':
if (--argc == 0) {
fatalError( "Option requires an argument: No file specified\n");
}
excludeList=realloc( excludeList, sizeof(char**) * (excludeListSize+1));
excludeList[excludeListSize] = *(++argv);
/* Remove leading "/"s */
if (*excludeList[excludeListSize] =='/') {
excludeList[excludeListSize] = (excludeList[excludeListSize])+1;
}
if (excludeList[excludeListSize++] == NULL)
fatalError( "Option requires an argument: No file specified\n");
stopIt=TRUE;
break;
case '-':
#if defined BB_FEATURE_TAR_EXCLUDE
if (strcmp(*argv, "-exclude")==0) {
if (--argc == 0) {
fatalError( "Option requires an argument: No file specified\n");
}
excludeList=realloc( excludeList, sizeof(char**) * (excludeListSize+2));
excludeList[excludeListSize] = *(++argv);
/* Remove leading "/"s */
if (*excludeList[excludeListSize] =='/') {
excludeList[excludeListSize] = (excludeList[excludeListSize])+1;
}
if (excludeList[excludeListSize++] == NULL)
fatalError( "Option requires an argument: No file specified\n");
/* Tack a NULL onto the end of the list */
excludeList[excludeListSize] = NULL;
stopIt=TRUE;
break;
}
#endif
usage(tar_usage);
break;
@ -249,13 +254,6 @@ extern int tar_main(int argc, char **argv)
}
}
}
#if 0
for (i=0; i<excludeListSize; i++) {
printf( "%s\n", excludeList[i]);
fflush(stdout);
}
#endif
/*
* Do the correct type of action supplying the rest of the
@ -336,6 +334,8 @@ tarExtractRegularFile(TarInfo *header, int extractFlag, int tostdoutFlag)
errorMsg(io_error, header->name, strerror(errno));
return( FALSE);
}
} else {
actualWriteSz=writeSize;
}
size -= actualWriteSz;
@ -532,11 +532,13 @@ readTarHeader(struct TarHeader *rawHeader, struct TarInfo *header)
static int readTarFile(const char* tarName, int extractFlag, int listFlag,
int tostdoutFlag, int verboseFlag, char** excludeList)
{
int status, tarFd=0;
int status, tarFd=-1;
int errorFlag=FALSE;
TarHeader rawHeader;
TarInfo header;
//int skipFileFlag=FALSE;
#if defined BB_FEATURE_TAR_EXCLUDE
char** tmpList;
#endif
/* Open the tar file for reading. */
if (!strcmp(tarName, "-"))
@ -557,7 +559,6 @@ static int readTarFile(const char* tarName, int extractFlag, int listFlag,
/* First, try to read the header */
if ( readTarHeader(&rawHeader, &header) == FALSE ) {
close( tarFd);
if ( *(header.name) == '\0' ) {
goto endgame;
} else {
@ -568,7 +569,34 @@ static int readTarFile(const char* tarName, int extractFlag, int listFlag,
}
if ( *(header.name) == '\0' )
goto endgame;
header.tarFd = tarFd;
#if defined BB_FEATURE_TAR_EXCLUDE
{
int skipFlag=FALSE;
/* Check for excluded files.... */
for (tmpList=excludeList; tmpList && *tmpList; tmpList++) {
/* Do some extra hoop jumping for when directory names
* end in '/' but the entry in tmpList doesn't */
if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || (
header.name[strlen(header.name)-1]=='/'
&& strncmp( *tmpList, header.name,
MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) {
/* If it is a regular file, pretend to extract it with
* the extractFlag set to FALSE, so the junk in the tarball
* is properly skipped over */
if ( header.type==REGTYPE || header.type==REGTYPE0 ) {
tarExtractRegularFile(&header, FALSE, FALSE);
}
skipFlag=TRUE;
break;
}
}
/* There are not the droids you're looking for, move along */
if (skipFlag==TRUE)
continue;
}
#endif
/* Special treatment if the list (-t) flag is on */
if (verboseFlag == TRUE && extractFlag == FALSE) {
int len, len1;
@ -623,13 +651,6 @@ static int readTarFile(const char* tarName, int extractFlag, int listFlag,
printf("\n");
}
#if 0
/* See if we want to restore this file or not */
skipFileFlag=FALSE;
if (wantFileName(outName) == FALSE) {
skipFileFlag = TRUE;
}
#endif
/* Remove any clutter lying in our way */
unlink( header.name);
@ -751,7 +772,9 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st
{
long chksum=0;
struct TarHeader header;
#if defined BB_FEATURE_TAR_EXCLUDE
char** tmpList;
#endif
const unsigned char *cp = (const unsigned char *) &header;
ssize_t size = sizeof(struct TarHeader);
@ -769,15 +792,23 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st
strncpy(header.name, fileName, sizeof(header.name));
}
/* Now that leading '/''s have been removed,
* check for excluded files.... */
#if defined BB_FEATURE_TAR_EXCLUDE
/* Check for excluded files.... */
for (tmpList=tbInfo->excludeList; tmpList && *tmpList; tmpList++) {
printf( "comparing '%s' and '%s'", *tmpList, header.name);
if (strcmp( *tmpList, header.name)==0)
printf( ": match\n");
else
printf( "\n");
/* Do some extra hoop jumping for when directory names
* end in '/' but the entry in tmpList doesn't */
if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || (
header.name[strlen(header.name)-1]=='/'
&& strncmp( *tmpList, header.name,
MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) {
/* Set the mode to something that is not a regular file, thereby
* faking out writeTarFile into thinking that nothing further need
* be done for this file. Yes, I know this is ugly, but it works. */
statbuf->st_mode = 0;
return( TRUE);
}
}
#endif
putOctal(header.mode, sizeof(header.mode), statbuf->st_mode);
putOctal(header.uid, sizeof(header.uid), statbuf->st_uid);
@ -965,6 +996,12 @@ static int writeTarFile(const char* tarName, int tostdoutFlag,
for (size=0; size<(2*TAR_BLOCK_SIZE); size++) {
write(tbInfo.tarFd, "\0", 1);
}
/* To be pedantically correct, we would check if the tarball
* is smaller then 20 tar blocks, and pad it if it was smaller,
* but that isn't necessary for GNU tar interoperability, and
* so is considered a waste of space */
/* Hang up the tools, close up shop, head home */
close(tarFd);
if (errorFlag == TRUE) {

View File

@ -180,6 +180,9 @@
// Enable support for creation of tar files.
#define BB_FEATURE_TAR_CREATE
//
// Enable support for "--exclude" for excluding files
//#define BB_FEATURE_TAR_EXCLUDE
//
//// Enable reverse sort
//#define BB_FEATURE_SORT_REVERSE
//

201
tar.c
View File

@ -52,33 +52,33 @@
#include <sys/param.h> /* for PATH_MAX */
static const char tar_usage[] =
#ifdef BB_FEATURE_TAR_CREATE
static const char tar_usage[] =
"tar -[cxtvOf] [tarFile] [-X excludeFile] [FILE] ...\n\n"
"Create, extract, or list files from a tar file. Note that\n"
"this version of tar packs hard links as separate files.\n\n"
"Options:\n"
"\tc=create, x=extract, t=list contents, v=verbose,\n"
"\tO=extract to stdout, f=tarfile or \"-\" for stdin\n"
"\tX=exclude file\n";
"tar -[cxtvO] "
#else
static const char tar_usage[] =
"tar -[xtvO] [-f tarFile] [-X excludeFile] [FILE] ...\n\n"
"Extract, or list files stored in a tar file. This\n"
"version of tar does not support creation of tar files.\n\n"
"Options:\n"
"\tx=extract, t=list contents, v=verbose,\n"
"\tO=extract to stdout, f=tarfile or \"-\" for stdin\n"
"\tX=exclude file\n";
"tar -[xtvO] "
#endif
#if defined BB_FEATURE_TAR_EXCLUDE
"[--exclude File] "
#endif
"[-f tarFile] [FILE] ...\n\n"
"Create, extract, or list files from a tar file. Note that\n"
"this version of tar treats hard links as separate files.\n\n"
"Main operation mode:\n"
#ifdef BB_FEATURE_TAR_CREATE
"\tc\t\tcreate\n"
#endif
"\tx\t\textract\n"
"\tt\t\tlist\n"
"File selection:\n"
"\tf\t\tname of tarfile or \"-\" for stdin\n"
"\tO\t\textract to stdout\n"
#if defined BB_FEATURE_TAR_EXCLUDE
"\t--exclude\tfile to exclude\n"
#endif
"Informative output:\n"
"\tv\t\tverbosely list files processed\n"
;
/* Tar file constants */
#ifndef MAJOR
@ -91,31 +91,30 @@ static const char tar_usage[] =
struct TarHeader
{
/* byte offset */
char name[100]; /* 0 */
char mode[8]; /* 100 */
char uid[8]; /* 108 */
char gid[8]; /* 116 */
char size[12]; /* 124 */
char mtime[12]; /* 136 */
char chksum[8]; /* 148 */
char typeflag; /* 156 */
char linkname[100]; /* 157 */
char magic[6]; /* 257 */
char version[2]; /* 263 */
char uname[32]; /* 265 */
char gname[32]; /* 297 */
char devmajor[8]; /* 329 */
char devminor[8]; /* 337 */
char prefix[155]; /* 345 */
/* padding 500 */
char name[100]; /* 0-99 */
char mode[8]; /* 100-107 */
char uid[8]; /* 108-115 */
char gid[8]; /* 116-123 */
char size[12]; /* 124-135 */
char mtime[12]; /* 136-147 */
char chksum[8]; /* 148-155 */
char typeflag; /* 156-156 */
char linkname[100]; /* 157-256 */
char magic[6]; /* 257-262 */
char version[2]; /* 263-264 */
char uname[32]; /* 265-296 */
char gname[32]; /* 297-328 */
char devmajor[8]; /* 329-336 */
char devminor[8]; /* 337-344 */
char prefix[155]; /* 345-499 */
char padding[12]; /* 500-512 (pad to exactly the TAR_BLOCK_SIZE) */
};
typedef struct TarHeader TarHeader;
/* A few useful constants */
#define TAR_MAGIC "ustar" /* ustar and a null */
//#define TAR_VERSION "00" /* 00 and no null */
#define TAR_VERSION " " /* Be compatable with old GNU format */
#define TAR_VERSION " " /* Be compatable with GNU tar format */
#define TAR_MAGIC_LEN 6
#define TAR_VERSION_LEN 2
#define TAR_BLOCK_SIZE 512
@ -170,7 +169,9 @@ static int writeTarFile(const char* tarName, int tostdoutFlag,
extern int tar_main(int argc, char **argv)
{
char** excludeList=NULL;
#if defined BB_FEATURE_TAR_EXCLUDE
int excludeListSize=0;
#endif
const char *tarName=NULL;
int listFlag = FALSE;
int extractFlag = FALSE;
@ -224,22 +225,26 @@ extern int tar_main(int argc, char **argv)
tostdoutFlag = TRUE;
tarName = "-";
break;
case 'X':
if (--argc == 0) {
fatalError( "Option requires an argument: No file specified\n");
}
excludeList=realloc( excludeList, sizeof(char**) * (excludeListSize+1));
excludeList[excludeListSize] = *(++argv);
/* Remove leading "/"s */
if (*excludeList[excludeListSize] =='/') {
excludeList[excludeListSize] = (excludeList[excludeListSize])+1;
}
if (excludeList[excludeListSize++] == NULL)
fatalError( "Option requires an argument: No file specified\n");
stopIt=TRUE;
break;
case '-':
#if defined BB_FEATURE_TAR_EXCLUDE
if (strcmp(*argv, "-exclude")==0) {
if (--argc == 0) {
fatalError( "Option requires an argument: No file specified\n");
}
excludeList=realloc( excludeList, sizeof(char**) * (excludeListSize+2));
excludeList[excludeListSize] = *(++argv);
/* Remove leading "/"s */
if (*excludeList[excludeListSize] =='/') {
excludeList[excludeListSize] = (excludeList[excludeListSize])+1;
}
if (excludeList[excludeListSize++] == NULL)
fatalError( "Option requires an argument: No file specified\n");
/* Tack a NULL onto the end of the list */
excludeList[excludeListSize] = NULL;
stopIt=TRUE;
break;
}
#endif
usage(tar_usage);
break;
@ -249,13 +254,6 @@ extern int tar_main(int argc, char **argv)
}
}
}
#if 0
for (i=0; i<excludeListSize; i++) {
printf( "%s\n", excludeList[i]);
fflush(stdout);
}
#endif
/*
* Do the correct type of action supplying the rest of the
@ -336,6 +334,8 @@ tarExtractRegularFile(TarInfo *header, int extractFlag, int tostdoutFlag)
errorMsg(io_error, header->name, strerror(errno));
return( FALSE);
}
} else {
actualWriteSz=writeSize;
}
size -= actualWriteSz;
@ -532,11 +532,13 @@ readTarHeader(struct TarHeader *rawHeader, struct TarInfo *header)
static int readTarFile(const char* tarName, int extractFlag, int listFlag,
int tostdoutFlag, int verboseFlag, char** excludeList)
{
int status, tarFd=0;
int status, tarFd=-1;
int errorFlag=FALSE;
TarHeader rawHeader;
TarInfo header;
//int skipFileFlag=FALSE;
#if defined BB_FEATURE_TAR_EXCLUDE
char** tmpList;
#endif
/* Open the tar file for reading. */
if (!strcmp(tarName, "-"))
@ -557,7 +559,6 @@ static int readTarFile(const char* tarName, int extractFlag, int listFlag,
/* First, try to read the header */
if ( readTarHeader(&rawHeader, &header) == FALSE ) {
close( tarFd);
if ( *(header.name) == '\0' ) {
goto endgame;
} else {
@ -568,7 +569,34 @@ static int readTarFile(const char* tarName, int extractFlag, int listFlag,
}
if ( *(header.name) == '\0' )
goto endgame;
header.tarFd = tarFd;
#if defined BB_FEATURE_TAR_EXCLUDE
{
int skipFlag=FALSE;
/* Check for excluded files.... */
for (tmpList=excludeList; tmpList && *tmpList; tmpList++) {
/* Do some extra hoop jumping for when directory names
* end in '/' but the entry in tmpList doesn't */
if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || (
header.name[strlen(header.name)-1]=='/'
&& strncmp( *tmpList, header.name,
MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) {
/* If it is a regular file, pretend to extract it with
* the extractFlag set to FALSE, so the junk in the tarball
* is properly skipped over */
if ( header.type==REGTYPE || header.type==REGTYPE0 ) {
tarExtractRegularFile(&header, FALSE, FALSE);
}
skipFlag=TRUE;
break;
}
}
/* There are not the droids you're looking for, move along */
if (skipFlag==TRUE)
continue;
}
#endif
/* Special treatment if the list (-t) flag is on */
if (verboseFlag == TRUE && extractFlag == FALSE) {
int len, len1;
@ -623,13 +651,6 @@ static int readTarFile(const char* tarName, int extractFlag, int listFlag,
printf("\n");
}
#if 0
/* See if we want to restore this file or not */
skipFileFlag=FALSE;
if (wantFileName(outName) == FALSE) {
skipFileFlag = TRUE;
}
#endif
/* Remove any clutter lying in our way */
unlink( header.name);
@ -751,7 +772,9 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st
{
long chksum=0;
struct TarHeader header;
#if defined BB_FEATURE_TAR_EXCLUDE
char** tmpList;
#endif
const unsigned char *cp = (const unsigned char *) &header;
ssize_t size = sizeof(struct TarHeader);
@ -769,15 +792,23 @@ writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *st
strncpy(header.name, fileName, sizeof(header.name));
}
/* Now that leading '/''s have been removed,
* check for excluded files.... */
#if defined BB_FEATURE_TAR_EXCLUDE
/* Check for excluded files.... */
for (tmpList=tbInfo->excludeList; tmpList && *tmpList; tmpList++) {
printf( "comparing '%s' and '%s'", *tmpList, header.name);
if (strcmp( *tmpList, header.name)==0)
printf( ": match\n");
else
printf( "\n");
/* Do some extra hoop jumping for when directory names
* end in '/' but the entry in tmpList doesn't */
if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || (
header.name[strlen(header.name)-1]=='/'
&& strncmp( *tmpList, header.name,
MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) {
/* Set the mode to something that is not a regular file, thereby
* faking out writeTarFile into thinking that nothing further need
* be done for this file. Yes, I know this is ugly, but it works. */
statbuf->st_mode = 0;
return( TRUE);
}
}
#endif
putOctal(header.mode, sizeof(header.mode), statbuf->st_mode);
putOctal(header.uid, sizeof(header.uid), statbuf->st_uid);
@ -965,6 +996,12 @@ static int writeTarFile(const char* tarName, int tostdoutFlag,
for (size=0; size<(2*TAR_BLOCK_SIZE); size++) {
write(tbInfo.tarFd, "\0", 1);
}
/* To be pedantically correct, we would check if the tarball
* is smaller then 20 tar blocks, and pad it if it was smaller,
* but that isn't necessary for GNU tar interoperability, and
* so is considered a waste of space */
/* Hang up the tools, close up shop, head home */
close(tarFd);
if (errorFlag == TRUE) {