1/* Handle so called `shell archives'. 2 Copyright (C) 1994, 1995 Free Software Foundation, Inc. 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 2, or (at your option) 7 any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program; if not, write to the Free Software Foundation, 16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17*/ 18 19/* Unpackage one or more shell archive files. The `unshar' program is a 20 filter which removes the front part of a file and passes the rest to 21 the `sh' command. It understands phrases like "cut here", and also 22 knows about shell comment characters and the Unix commands `echo', 23 `cat', and `sed'. */ 24 25#include "system.h" 26#include "getopt.h" 27 28/* Buffer size for holding a file name. FIXME: No fix limit in GNU... */ 29#define NAME_BUFFER_SIZE 1024 30 31/* Buffer size for shell process input. */ 32#define SHELL_BUFFER_SIZE 8196 33 34#define EOL '\n' 35 36/* The name this program was run with. */ 37const char *program_name; 38 39/* If non-zero, display usage information and exit. */ 40static int show_help = 0; 41 42/* If non-zero, print the version on standard output and exit. */ 43static int show_version = 0; 44 45static int pass_c_flag = 0; 46static int continue_reading = 0; 47static const char *exit_string = "exit 0"; 48static size_t exit_string_length; 49static char *current_directory; 50 51/*-------------------------------------------------------------------. 52| Match the leftmost part of a string. Returns 1 if initial | 53| characters of DATA match PATTERN exactly; else 0. This was | 54| formerly a function. But because we always have a constant string | 55| as the seconf argument and the length of the second argument is a | 56| lot of shorter than the buffer the first argument is pointing at, | 57| we simply use `memcmp'. And one more point: even if the `memcmp' | 58| function does not work correct for 8 bit characters it does not | 59| matter here. We are only interested in equal or not equal | 60| information. | 61`-------------------------------------------------------------------*/ 62 63#define starting_with(data, pattern) \ 64 (memcmp (data, pattern, sizeof (pattern) - 1) == 0) 65 66 67/*-------------------------------------------------------------------------. 68| For a DATA string and a PATTERN containing one or more embedded | 69| asterisks (matching any number of characters), return non-zero if the | 70| match succeeds, and set RESULT_ARRAY[I] to the characters matched by the | 71| I'th *. | 72`-------------------------------------------------------------------------*/ 73 74static int 75matched_by (data, pattern, result_array) 76 const char *data; 77 const char *pattern; 78 char **result_array; 79{ 80 const char *pattern_cursor = NULL; 81 const char *data_cursor = NULL; 82 char *result_cursor = NULL; 83 int number_of_results = 0; 84 85 while (1) 86 if (*pattern == '*') 87 { 88 pattern_cursor = ++pattern; 89 data_cursor = data; 90 result_cursor = result_array[number_of_results++]; 91 *result_cursor = '\0'; 92 } 93 else if (*data == *pattern) 94 { 95 if (*pattern == '\0') 96 /* The pattern matches. */ 97 return 1; 98 99 pattern++; 100 data++; 101 } 102 else 103 { 104 if (*data == '\0') 105 /* The pattern fails: no more data. */ 106 return 0; 107 108 if (pattern_cursor == NULL) 109 /* The pattern fails: no star to adjust. */ 110 return 0; 111 112 /* Restart pattern after star. */ 113 114 pattern = pattern_cursor; 115 *result_cursor++ = *data_cursor; 116 *result_cursor = '\0'; 117 118 /* Rescan after copied char. */ 119 120 data = ++data_cursor; 121 } 122} 123 124/*------------------------------------------------------------------------. 125| Associated with a given file NAME, position FILE at the start of the | 126| shell command portion of a shell archive file. Scan file from position | 127| START. | 128`------------------------------------------------------------------------*/ 129 130static int 131find_archive (name, file, start) 132 const char *name; 133 FILE *file; 134 off_t start; 135{ 136 char buffer[BUFSIZ]; 137 off_t position; 138 139 /* Results from star matcher. */ 140 141 static char res1[BUFSIZ], res2[BUFSIZ], res3[BUFSIZ], res4[BUFSIZ]; 142 static char *result[] = {res1, res2, res3, res4}; 143 144 fseek (file, start, 0); 145 146 while (1) 147 { 148 149 /* Record position of the start of this line. */ 150 151 position = ftell (file); 152 153 /* Read next line, fail if no more and no previous process. */ 154 155 if (!fgets (buffer, BUFSIZ, file)) 156 { 157 if (!start) 158 error (0, 0, _("Found no shell commands in %s"), name); 159 return 0; 160 } 161 162 /* Bail out if we see C preprocessor commands or C comments. */ 163 164 if (starting_with (buffer, "#include") 165 || starting_with (buffer, "# include") 166 || starting_with (buffer, "#define") 167 || starting_with (buffer, "# define") 168 || starting_with (buffer, "#ifdef") 169 || starting_with (buffer, "# ifdef") 170 || starting_with (buffer, "#ifndef") 171 || starting_with (buffer, "# ifndef") 172 || starting_with (buffer, "/*")) 173 { 174 error (0, 0, _("%s looks like raw C code, not a shell archive"), 175 name); 176 return 0; 177 } 178 179 /* Does this line start with a shell command or comment. */ 180 181 if (starting_with (buffer, "#") 182 || starting_with (buffer, ":") 183 || starting_with (buffer, "echo ") 184 || starting_with (buffer, "sed ") 185 || starting_with (buffer, "cat ") 186 || starting_with (buffer, "if ")) 187 { 188 fseek (file, position, 0); 189 return 1; 190 } 191 192 /* Does this line say "Cut here". */ 193 194 if (matched_by (buffer, "*CUT*HERE*", result) || 195 matched_by (buffer, "*cut*here*", result) || 196 matched_by (buffer, "*TEAR*HERE*", result) || 197 matched_by (buffer, "*tear*here*", result) || 198 matched_by (buffer, "*CUT*CUT*", result) || 199 matched_by (buffer, "*cut*cut*", result)) 200 { 201 202 /* Read next line after "cut here", skipping blank lines. */ 203 204 while (1) 205 { 206 position = ftell (file); 207 208 if (!fgets (buffer, BUFSIZ, file)) 209 { 210 error (0, 0, _("Found no shell commands after `cut' in %s"), 211 name); 212 return 0; 213 } 214 215 if (*buffer != '\n') 216 break; 217 } 218 219 /* Win if line starts with a comment character of lower case 220 letter. */ 221 222 if (*buffer == '#' || *buffer == ':' 223 || (('a' <= *buffer) && ('z' >= *buffer))) 224 { 225 fseek (file, position, 0); 226 return 1; 227 } 228 229 /* Cut here message lied to us. */ 230 231 error (0, 0, _("%s is probably not a shell archive"), name); 232 error (0, 0, _("The `cut' line was followed by: %s"), buffer); 233 return 0; 234 } 235 } 236} 237 238/*-----------------------------------------------------------------. 239| Unarchive a shar file provided on file NAME. The file itself is | 240| provided on the already opened FILE. | 241`-----------------------------------------------------------------*/ 242 243static void 244unarchive_shar_file (name, file) 245 const char *name; 246 FILE *file; 247{ 248 char buffer[SHELL_BUFFER_SIZE]; 249 FILE *shell_process; 250 off_t current_position = 0; 251 char *more_to_read; 252 253 while (find_archive (name, file, current_position)) 254 { 255 printf ("%s:\n", name); 256 shell_process = popen (pass_c_flag ? "sh -s - -c" : "sh", "w"); 257 if (!shell_process) 258 error (EXIT_FAILURE, errno, _("Starting `sh' process")); 259 260 if (!continue_reading) 261 { 262 size_t len; 263 264 while ((len = fread (buffer, 1, SHELL_BUFFER_SIZE, file)) != 0) 265 fwrite (buffer, 1, len, shell_process); 266#if 0 267 /* Don't know whether a test is appropriate here. */ 268 if (ferror (shell_process) != 0) 269 fwrite (buffer, length, 1, shell_process); 270#endif 271 pclose (shell_process); 272 break; 273 } 274 else 275 { 276 while (more_to_read = fgets (buffer, SHELL_BUFFER_SIZE, file), 277 more_to_read != NULL) 278 { 279 fputs (buffer, shell_process); 280 if (!strncmp (exit_string, buffer, exit_string_length)) 281 break; 282 } 283 pclose (shell_process); 284 285 if (more_to_read) 286 current_position = ftell (file); 287 else 288 break; 289 } 290 } 291} 292 293/*-----------------------------. 294| Explain how to use program. | 295`-----------------------------*/ 296 297static void 298usage (status) 299 int status; 300{ 301 if (status != EXIT_SUCCESS) 302 fprintf (stderr, _("Try `%s --help' for more information.\n"), 303 program_name); 304 else 305 { 306 printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); 307 fputs (_("\ 308Mandatory arguments to long options are mandatory for short options too.\n\ 309\n\ 310 -d, --directory=DIRECTORY change to DIRECTORY before unpacking\n\ 311 -c, --overwrite pass -c to shar script for overwriting files\n\ 312 -e, --exit-0 same as `--split-at=\"exit 0\"'\n\ 313 -E, --split-at=STRING split concatenated shars after STRING\n\ 314 -f, --force same as `-c'\n\ 315 --help display this help and exit\n\ 316 --version output version information and exit\n\ 317\n\ 318If no FILE, standard input is read.\n"), 319 stdout); 320 } 321 exit (status); 322} 323 324/*--------------------------------------. 325| Decode options and launch execution. | 326`--------------------------------------*/ 327 328static const struct option long_options[] = 329{ 330 {"directory", required_argument, NULL, 'd'}, 331 {"exit-0", no_argument, NULL, 'e'}, 332 {"force", no_argument, NULL, 'f'}, 333 {"overwrite", no_argument, NULL, 'c'}, 334 {"split-at", required_argument, NULL, 'E'}, 335 336 {"help", no_argument, &show_help, 1}, 337 {"version", no_argument, &show_version, 1}, 338 339 { NULL, 0, NULL, 0 }, 340}; 341 342int 343main (argc, argv) 344 int argc; 345 char *const *argv; 346{ 347 size_t size_read; 348 FILE *file; 349 char name_buffer[NAME_BUFFER_SIZE]; 350 char copy_buffer[NAME_BUFFER_SIZE]; 351 int optchar; 352 353 program_name = argv[0]; 354 setlocale (LC_ALL, ""); 355 356 /* Set the text message domain. */ 357 bindtextdomain (PACKAGE, LOCALEDIR); 358 textdomain (PACKAGE); 359 360#ifdef __MSDOS__ 361 setbuf (stdout, NULL); 362 setbuf (stderr, NULL); 363#endif 364 365 if (current_directory = xgetcwd (), !current_directory) 366 error (EXIT_FAILURE, errno, _("Cannot get current directory name")); 367 368 /* Process options. */ 369 370 while (optchar = getopt_long (argc, argv, "E:cd:ef", long_options, NULL), 371 optchar != EOF) 372 switch (optchar) 373 { 374 case '\0': 375 break; 376 377 case 'c': 378 case 'f': 379 pass_c_flag = 1; 380 break; 381 382 case 'd': 383 if (chdir (optarg) == -1) 384 error (2, 0, _("Cannot chdir to `%s'"), optarg); 385 break; 386 387 case 'E': 388 exit_string = optarg; 389 /* Fall through. */ 390 391 case 'e': 392 continue_reading = 1; 393 exit_string_length = strlen (exit_string); 394 break; 395 396 default: 397 usage (EXIT_FAILURE); 398 } 399 400 if (show_version) 401 { 402 printf ("%s - GNU %s %s\n", program_name, PACKAGE, VERSION); 403 exit (EXIT_SUCCESS); 404 } 405 406 if (show_help) 407 usage (EXIT_SUCCESS); 408 409 if (optind < argc) 410 for (; optind < argc; optind++) 411 { 412 if (argv[optind][0] == '/') 413 stpcpy (name_buffer, argv[optind]); 414 else 415 { 416 char *cp = stpcpy (name_buffer, current_directory); 417 *cp++ = '/'; 418 stpcpy (cp, argv[optind]); 419 } 420 if (file = fopen (name_buffer, "r"), !file) 421 error (EXIT_FAILURE, errno, name_buffer); 422 unarchive_shar_file (name_buffer, file); 423 fclose (file); 424 } 425 else 426 { 427 sprintf (name_buffer, "/tmp/unsh.%05d", (int) getpid ()); 428 unlink (name_buffer); 429 430 if (file = fopen (name_buffer, "w+"), !file) 431 error (EXIT_FAILURE, errno, name_buffer); 432#ifndef __MSDOS__ 433 unlink (name_buffer); /* will be deleted on fclose */ 434#endif 435 436 while (size_read = fread (copy_buffer, 1, sizeof (copy_buffer), stdin), 437 size_read != 0) 438 fwrite (copy_buffer, size_read, 1, file); 439 rewind (file); 440 441 unarchive_shar_file (_("standard input"), file); 442 443 fclose (file); 444#ifdef __MSDOS__ 445 unlink (name_buffer); 446#endif 447 } 448 449 exit (EXIT_SUCCESS); 450} 451