diff options
author | Todd C. Miller <millert@cvs.openbsd.org> | 1997-09-01 21:48:29 +0000 |
---|---|---|
committer | Todd C. Miller <millert@cvs.openbsd.org> | 1997-09-01 21:48:29 +0000 |
commit | 064be4cc3d65db65e478b121f6763b25be725f82 (patch) | |
tree | b92ee629ae97ec5848da2224fc4a480878f9c216 /usr.sbin/pppd/chat | |
parent | b1287d1c9900948cbc62d79e882e4a5d02e57558 (diff) |
Update from ppp 3.2.1
Diffstat (limited to 'usr.sbin/pppd/chat')
-rw-r--r-- | usr.sbin/pppd/chat/chat.8 | 165 | ||||
-rw-r--r-- | usr.sbin/pppd/chat/chat.c | 579 |
2 files changed, 622 insertions, 122 deletions
diff --git a/usr.sbin/pppd/chat/chat.8 b/usr.sbin/pppd/chat/chat.8 index cd995a9daee..cbfa10a2260 100644 --- a/usr.sbin/pppd/chat/chat.8 +++ b/usr.sbin/pppd/chat/chat.8 @@ -1,6 +1,7 @@ .\" -*- nroff -*- -.\" manual page [] for chat 1.8 -.\" $Id: chat.8,v 1.2 1997/05/31 06:03:09 deraadt Exp $ +.\" manual page [] for chat 1.9 +.\" Id: chat.8,v 1.5 1997/07/14 03:49:41 paulus Exp $ +.\" $OpenBSD: chat.8,v 1.3 1997/09/01 21:48:27 millert Exp $ .\" SH section heading .\" SS subsection heading .\" LP paragraph @@ -43,11 +44,27 @@ Set the file for output of the report strings. If you use the keyword option is not used and you still use \fIREPORT\fR keywords, the \fIstderr\fR file is used for the report strings. .TP +.B -e +Start with the echo option turned on. Echoing may also be turned on +or off at specific points in the chat script by using the \fIECHO\fR +keyword. When echoing is enabled, all output from the modem is echoed +to \fIstderr\fR. +.TP .B -v Request that the \fIchat\fR script be executed in a verbose mode. The \fIchat\fR program will then log all text received from the modem and the output strings which it sends to the SYSLOG. .TP +.B -V +Request that the \fIchat\fR script be executed in a stderr verbose +mode. The \fIchat\fR program will then log all text received from the +modem and the output strings which it sends to the stderr device. This +device is usually the local console at the station running the chat or +pppd program. This option will not work properly if the stderr is +redirected to the /dev/null location as is the case should pppd be run +in the 'detached' mode. In that case, use the '-v' option to record +the session on the SYSLOG device. +.TP .B script If the script is not specified in a file with the \fI-f\fR option then the script is included as parameters to the \fIchat\fR program. @@ -105,6 +122,20 @@ for the same login: prompt, however, if one was not received, a single return sequence is sent and then it will look for login: again. Should line noise obscure the first login prompt then sending the empty line will usually generate a login prompt again. +.SH COMMENTS +Comments can be embedded in the chat script. A comment is a line which +starts with the \fB#\fR (hash) character in column 1. Such comment +lines are just ignored by the chat program. If a '#' character is to +be expected as the first character of the expect sequence, you should +quote the expect string. +If you want to wait for a prompt that starts with a # (hash) +character, you would have to write something like this: +.IP +# Now wait for the prompt and send logout string +.br +\'# ' logout +.LP + .SH ABORT STRINGS Many modems will report the status of the call as a string. These strings may be \fBCONNECTED\fR or \fBNO CARRIER\fR or \fBBUSY\fR. It @@ -128,6 +159,60 @@ character sequence. The script will then fail because it found a match to the abort string. If it received the string \fINO CARRIER\fR, it will abort for the same reason. Either string may be received. Either string will terminate the \fIchat\fR script. +.SH CLR_ABORT STRINGS +This sequence allows for clearing previously set \fBABORT\fR strings. +\fBABORT\fR strings are kept in an array of a pre-determined size (at +compilation time); \fBCLR_ABORT\fR will reclaim the space for cleared +entries so that new strings can use that space. +.SH SAY STRINGS +The \fBSAY\fR directive allows the script to send strings to the user +at the terminal via standard error. If \fBchat\fR is being run by +pppd, and pppd is running as a daemon (detached from its controlling +terminal), standard error will normally be redirected to the file +/etc/ppp/connect-errors. +.LP +\fBSAY\fR strings must be enclosed in single or double quotes. If +carriage return and line feed are needed in the string to be output, +you must explicitely add them to your string. +.LP +The SAY strings could be used to give progress messages in sections of +the script where you want to have 'ECHO OFF' but still let the user +know what is happening. An example is: +.IP +ABORT BUSY +.br +ECHO OFF +.br +SAY "Dialling your ISP...\\n" +.br +\'' ATDT5551212 +.br +TIMEOUT 120 +.br +SAY "Waiting up to 2 minutes for connection ... " +.br +CONNECT '' +.br +SAY "Connected, now logging in ...\n" +.br +ogin: account +.br +ssword: pass +.br +$ \c +SAY "Logged in OK ...\n" +\fIetc ...\fR +.LP +This sequence will only present the SAY strings to the user and all +the details of the script will remain hidden. For example, if the +above script works, the user will see: +.IP +Dialling your ISP... +.br +Waiting up to 2 minutes for connection ... Connected, now logging in ... +.br +Logged in OK ... +.LP .SH REPORT STRINGS A \fBreport\fR string is similar to the ABORT string. The difference @@ -154,6 +239,82 @@ ATDT5551212 to dial the telephone. The expected string is of the script is executed. In addition the program will write to the expect-file the string "CONNECT" plus any characters which follow it such as the connection rate. +.SH CLR_REPORT STRINGS +This sequence allows for clearing previously set \fBREPORT\fR strings. +\fBREPORT\fR strings are kept in an array of a pre-determined size (at +compilation time); \fBCLR_REPORT\fR will reclaim the space for cleared +entries so that new strings can use that space. +.SH ECHO +The echo options controls whether the output from the modem is echoed +to \fIstderr\fR. This option may be set with the \fI-e\fR option, but +it can also be controlled by the \fIECHO\fR keyword. The "expect-send" +pair \fIECHO\fR \fION\fR enables echoing, and \fIECHO\fR \fIOFF\fR +disables it. With this keyword you can select which parts of the +conversation should be visible. For instance, with the following +script: +.IP +ABORT 'BUSY' +.br +ABORT 'NO CARRIER' +.br +'' ATZ +.br +OK\\r\\n ATD1234567 +.br +\\r\\n \\c +.br +ECHO ON +.br +CONNECT \\c +.br +ogin: account +.LP +all output resulting from modem configuration and dialing is not visible, +but starting with the \fICONNECT\fR (or \fIBUSY\fR) message, everything +will be echoed. +.SH HANGUP +The HANGUP options control whether a modem hangup should be considered +as an error or not. This option is useful in scripts for dialling +systems which will hang up and call your system back. The HANGUP +options can be \fBON\fR or \fBOFF\fR. +.br +When HANGUP is set OFF and the modem hangs up (e.g., after the first +stage of logging in to a callback system), \fBchat\fR will continue +running the script (e.g., waiting for the incoming call and second +stage login prompt). As soon as the incoming call is connected, you +should use the \fBHANGUP ON\fR directive to reinstall normal hang up +signal behavior. Here is an (simple) example script: +.IP +ABORT 'BUSY' +.br +'' ATZ +.br +OK\\r\\n ATD1234567 +.br +\\r\\n \\c +.br +CONNECT \\c +.br +\'Callback login:' call_back_ID +.br +HANGUP OFF +.br +ABORT "Bad Login" +.br +\'Callback Password:' Call_back_password +.br +TIMEOUT 120 +.br +CONNECT \\c +.br +HANGUP ON +.br +ABORT "NO CARRIER" +.br +ogin:--BREAK--ogin: real_account +.br +\fIetc ...\fR +.LP .SH TIMEOUT The initial timeout value is 45 seconds. This may be changed using the \fB-t\fR parameter. diff --git a/usr.sbin/pppd/chat/chat.c b/usr.sbin/pppd/chat/chat.c index 79efb648fa2..1bdeac1c25a 100644 --- a/usr.sbin/pppd/chat/chat.c +++ b/usr.sbin/pppd/chat/chat.c @@ -1,3 +1,5 @@ +/* $OpenBSD: chat.c,v 1.3 1997/09/01 21:48:28 millert Exp $ */ + /* * Chat -- a program for automatic session establishment (i.e. dial * the phone and log in). @@ -13,14 +15,51 @@ * * This software is in the public domain. * - * Please send all bug reports, requests for information, etc. to: + * ----------------- + * + * Added SAY keyword to send output to stderr. + * This allows to turn ECHO OFF and to output specific, user selected, + * text to give progress messages. This best works when stderr + * exists (i.e.: pppd in nodetach mode). + * + * Added HANGUP directives to allow for us to be called + * back. When HANGUP is set to NO, chat will not hangup at HUP signal. + * We rely on timeouts in that case. + * + * Added CLR_ABORT to clear previously set ABORT string. This has been + * dictated by the HANGUP above as "NO CARRIER" (for example) must be + * an ABORT condition until we know the other host is going to close + * the connection for call back. As soon as we have completed the + * first stage of the call back sequence, "NO CARRIER" is a valid, non + * fatal string. As soon as we got called back (probably get "CONNECT"), + * we should re-arm the ABORT "NO CARRIER". Hence the CLR_ABORT command. + * Note that CLR_ABORT packs the abort_strings[] array so that we do not + * have unused entries not being reclaimed. + * + * In the same vein as above, added CLR_REPORT keyword. + * + * Allow for comments. Line starting with '#' are comments and are + * ignored. If a '#' is to be expected as the first character, the + * expect string must be quoted. + * + * + * Francis Demierre <Francis@SwissMail.Com> + * Thu May 15 17:15:40 MET DST 1997 * - * Al Longyear (longyear@netcom.com) - * (I was the last person to change this code.) * * Added -r "report file" switch & REPORT keyword. * Robert Geer <bgeer@xmission.com> * + * + * Added -e "echo" switch & ECHO keyword + * Dick Streefland <dicks@tasking.nl> + * + * + * Considerable updates and modifications by + * Al Longyear <longyear@pobox.com> + * Paul Mackerras <paulus@cs.anu.edu.au> + * + * * The original author is: * * Karl Fox <karl@MorningStar.Com> @@ -29,17 +68,26 @@ * Columbus, OH 43221 * (614)451-1883 * + * */ -static char rcsid[] = "$Id: chat.c,v 1.2 1996/05/21 20:53:56 deraadt Exp $"; +#ifndef lint +#if 0 +static char rcsid[] = "Id: chat.c,v 1.15 1997/07/14 03:50:22 paulus Exp $"; +#else +static char rcsid[] = "$OpenBSD: chat.c,v 1.3 1997/09/01 21:48:28 millert Exp $"; +#endif +#endif #include <stdio.h> +#include <ctype.h> #include <time.h> #include <fcntl.h> #include <signal.h> #include <errno.h> #include <string.h> #include <stdlib.h> +#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <syslog.h> @@ -92,7 +140,9 @@ char *program_name; #define MAX_REPORTS 50 #define DEFAULT_CHAT_TIMEOUT 45 +int echo = 0; int verbose = 0; +int Verbose = 0; int quiet = 0; int report = 0; int exit_code = 0; @@ -119,11 +169,15 @@ struct termios saved_tty_parameters; char *abort_string[MAX_ABORTS], *fail_reason = (char *)0, fail_buffer[50]; -int n_aborts = 0, abort_next = 0, timeout_next = 0; +int n_aborts = 0, abort_next = 0, timeout_next = 0, echo_next = 0; +int clear_abort_next = 0; char *report_string[MAX_REPORTS] ; char report_buffer[50] ; int n_reports = 0, report_next = 0, report_gathering = 0 ; +int clear_report_next = 0; + +int say_next = 0, hup_next = 0; void *dup_mem __P((void *b, size_t c)); void *copy_of __P((char *s)); @@ -139,6 +193,7 @@ SIGTYPE sighup __P((int signo)); void unalarm __P((void)); void init __P((void)); void set_tty_parameters __P((void)); +void echo_stderr __P((int)); void break_sequence __P((void)); void terminate __P((int status)); void do_file __P((char *chat_file)); @@ -154,6 +209,10 @@ char *clean __P((register char *s, int sending)); void break_sequence __P((void)); void terminate __P((int status)); void die __P((void)); +void pack_array __P((char **array, int end)); +char *expect_strtok __P((char *, char *)); + +int main __P((int, char *[])); void *dup_mem(b, c) void *b; @@ -191,16 +250,24 @@ char **argv; program_name = *argv; tzset(); - while (option = OPTION(argc, argv)) + while ((option = OPTION(argc, argv)) != 0) { switch (option) { + case 'e': + ++echo; + break; + case 'v': ++verbose; break; + case 'V': + ++Verbose; + break; + case 'f': - if (arg = OPTARG(argc, argv)) + if ((arg = OPTARG(argc, argv)) != NULL) { chat_file = copy_of(arg); } @@ -211,7 +278,7 @@ char **argv; break; case 't': - if (arg = OPTARG(argc, argv)) + if ((arg = OPTARG(argc, argv)) != NULL) { timeout = atoi(arg); } @@ -287,11 +354,11 @@ char **argv; } else { - while (arg = ARG(argc, argv)) + while ((arg = ARG(argc, argv)) != NULL) { chat_expect(arg); - if (arg = ARG(argc, argv)) + if ((arg = ARG(argc, argv)) != NULL) { chat_send(arg); } @@ -299,6 +366,7 @@ char **argv; } terminate(0); + return 0; } /* @@ -333,8 +401,11 @@ char *chat_file; linect++; sp = buf; - if ( *sp == '#') - continue; + + /* lines starting with '#' are comments. If a real '#' + is to be expected, it should be quoted .... */ + if ( *sp == '#' ) continue; + while (*sp != '\0') { if (*sp == ' ' || *sp == '\t') @@ -399,7 +470,7 @@ char *chat_file; void usage() { fprintf(stderr, "\ -Usage: %s [-v] [-t timeout] [-r report-file] {-f chat-file | chat-script}\n", +Usage: %s [-e] [-v] [-t timeout] [-r report-file] {-f chat-file | chat-script}\n", program_name); exit(1); } @@ -409,16 +480,20 @@ char *p; void logf (str) const char *str; - { - p = line + strlen(line); - strcat (p, str); +{ + int l = strlen(line); - if (str[strlen(str)-1] == '\n') - { + if (l + strlen(str) >= sizeof(line)) { + syslog(LOG_INFO, "%s", line); + l = 0; + } + strcpy(line + l, str); + + if (str[strlen(str)-1] == '\n') { syslog (LOG_INFO, "%s", line); line[0] = 0; - } } +} void logflush() { @@ -575,14 +650,34 @@ void break_sequence() void terminate(status) int status; { + echo_stderr(-1); if (report_file != (char *) 0 && report_fp != (FILE *) NULL) - { + { +/* + * Allow the last of the report string to be gathered before we terminate. + */ + if (report_gathering) { + int c, rep_len; + + rep_len = strlen(report_buffer); + while (rep_len + 1 <= sizeof(report_buffer)) { + alarm(1); + c = get_char(); + alarm(0); + if (c < 0 || iscntrl(c)) + break; + report_buffer[rep_len] = c; + ++rep_len; + } + report_buffer[rep_len] = 0; + fprintf (report_fp, "chat: %s\n", report_buffer); + } if (verbose) { fprintf (report_fp, "Closing \"%s\".\n", report_file); } fclose (report_fp); - report_fp = (FILE*) NULL; + report_fp = (FILE *) NULL; } #if defined(get_term_param) @@ -678,7 +773,7 @@ int sending; break; case 'q': - quiet = ! quiet; + quiet = 1; break; case 'r': @@ -755,104 +850,173 @@ int sending; } /* + * A modified version of 'strtok'. This version skips \ sequences. + */ + +char *expect_strtok (s, term) +char *s, *term; + { + static char *str = ""; + int escape_flag = 0; + char *result; +/* + * If a string was specified then do initial processing. + */ + if (s) + { + str = s; + } +/* + * If this is the escape flag then reset it and ignore the character. + */ + if (*str) + { + result = str; + } + else + { + result = (char *) 0; + } + + while (*str) + { + if (escape_flag) + { + escape_flag = 0; + ++str; + continue; + } + + if (*str == '\\') + { + ++str; + escape_flag = 1; + continue; + } +/* + * If this is not in the termination string, continue. + */ + if (strchr (term, *str) == (char *) 0) + { + ++str; + continue; + } +/* + * This is the terminator. Mark the end of the string and stop. + */ + *str++ = '\0'; + break; + } + return (result); + } + +/* * Process the expect string */ -void chat_expect(s) -register char *s; + +void chat_expect (s) +char *s; { + char *expect; + char *reply; + + if (strcmp(s, "HANGUP") == 0) + { + ++hup_next; + return; + } + if (strcmp(s, "ABORT") == 0) { ++abort_next; return; } + if (strcmp(s, "CLR_ABORT") == 0) + { + ++clear_abort_next; + return; + } + if (strcmp(s, "REPORT") == 0) { ++report_next; return; } + if (strcmp(s, "CLR_REPORT") == 0) + { + ++clear_report_next; + return; + } + if (strcmp(s, "TIMEOUT") == 0) { ++timeout_next; return; } - while (*s) + if (strcmp(s, "ECHO") == 0) { - register char *hyphen; + ++echo_next; + return; + } + if (strcmp(s, "SAY") == 0) + { + ++say_next; + return; + } +/* + * Fetch the expect and reply string. + */ + for (;;) + { + expect = expect_strtok (s, "-"); + s = (char *) 0; - for (hyphen = s; *hyphen; ++hyphen) + if (expect == (char *) 0) { - if (*hyphen == '-') - { - if (hyphen == s || hyphen[-1] != '\\') - { - break; - } - } + return; } - - if (*hyphen == '-') - { - *hyphen = '\0'; - if (get_string(s)) - { - return; - } - else - { - s = hyphen + 1; - - for (hyphen = s; *hyphen; ++hyphen) - { - if (*hyphen == '-') - { - if (hyphen == s || hyphen[-1] != '\\') - { - break; - } - } - } - - if (*hyphen == '-') - { - *hyphen = '\0'; - - chat_send(s); - s = hyphen + 1; - } - else - { - chat_send(s); - return; - } - } + reply = expect_strtok (s, "-"); +/* + * Handle the expect string. If successful then exit. + */ + if (get_string (expect)) + { + return; } - else +/* + * If there is a sub-reply string then send it. Otherwise any condition + * is terminal. + */ + if (reply == (char *) 0 || exit_code != 3) { - if (get_string(s)) - { - return; - } - else - { - if (fail_reason) - { - syslog(LOG_INFO, "Failed (%s)", fail_reason); - } - else - { - syslog(LOG_INFO, "Failed"); - } - - terminate(exit_code); - } + break; } + + chat_send (reply); + } +/* + * The expectation did not occur. This is terminal. + */ + if (fail_reason) + { + syslog(LOG_INFO, "Failed (%s)", fail_reason); + } + else + { + syslog(LOG_INFO, "Failed"); } + terminate(exit_code); } +/* + * Translate the input character to the appropriate string for printing + * the data. + */ + char *character(c) int c; { @@ -887,6 +1051,29 @@ int c; void chat_send (s) register char *s; { + if (say_next) + { + say_next = 0; + s = clean(s,0); + write(2, s, strlen(s)); + free(s); + return; + } + if (hup_next) + { + hup_next = 0; + if (strcmp(s, "OFF") == 0) + signal(SIGHUP, SIG_IGN); + else + signal(SIGHUP, sighup); + return; + } + if (echo_next) + { + echo_next = 0; + echo = (strcmp(s, "ON") == 0); + return; + } if (abort_next) { char *s1; @@ -923,6 +1110,52 @@ register char *s; return; } + if (clear_abort_next) + { + char *s1; + char *s2 = s; + int i; + int old_max; + int pack = 0; + + clear_abort_next = 0; + + s1 = clean(s, 0); + + if (strlen(s1) > strlen(s) + || strlen(s1) + 1 > sizeof(fail_buffer)) + { + syslog(LOG_WARNING, "Illegal or too-long CLR_ABORT string ('%s')", s); + die(); + } + + old_max = n_aborts; + for (i=0; i < n_aborts; i++) + { + if ( strcmp(s1,abort_string[i]) == 0 ) + { + free(abort_string[i]); + abort_string[i] = NULL; + pack++; + n_aborts--; + if (verbose) + { + logf("clear abort on ("); + + for (s2 = s; *s2; ++s2) + { + logf(character(*s2)); + } + + logf(")\n"); + } + } + } + free(s1); + if (pack) pack_array(abort_string,old_max); + return; + } + if (report_next) { char *s1; @@ -957,6 +1190,52 @@ register char *s; return; } + if (clear_report_next) + { + char *s1; + char *s2 = s; + int i; + int old_max; + int pack = 0; + + clear_report_next = 0; + + s1 = clean(s, 0); + + if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1) + { + syslog(LOG_WARNING, "Illegal or too-long REPORT string ('%s')", s); + die(); + } + + old_max = n_reports; + for (i=0; i < n_reports; i++) + { + if ( strcmp(s1,report_string[i]) == 0 ) + { + free(report_string[i]); + report_string[i] = NULL; + pack++; + n_reports--; + if (verbose) + { + logf("clear report ("); + + for (s2 = s; *s2; ++s2) + { + logf(character(*s2)); + } + + logf(")\n"); + } + } + } + free(s1); + if (pack) pack_array(report_string,old_max); + + return; + } + if (timeout_next) { timeout_next = 0; @@ -1091,6 +1370,7 @@ int c; int put_string (s) register char *s; { + quiet = 0; s = clean(s, 1); if (verbose) @@ -1157,6 +1437,37 @@ register char *s; } /* + * Echo a character to stderr. + * When called with -1, a '\n' character is generated when + * the cursor is not at the beginning of a line. + */ +void echo_stderr(n) +int n; + { + static int need_lf; + char *s; + + switch (n) + { + case '\r': /* ignore '\r' */ + break; + case -1: + if (need_lf == 0) + break; + /* fall through */ + case '\n': + write(2, "\n", 1); + need_lf = 0; + break; + default: + s = character(n); + write(2, s, strlen(s)); + need_lf = 1; + break; + } + } + +/* * 'Wait for' this string to appear on this file descriptor. */ int get_string(string) @@ -1209,6 +1520,10 @@ register char *string; { int n, abort_len, report_len; + if (echo) + { + echo_stderr(c); + } if (verbose) { if (c == '\n') @@ -1221,39 +1536,12 @@ register char *string; } } - *s++ = c; - - if (s - temp >= len && - c == string[len - 1] && - strncmp(s - len, string, len) == 0) - { - if (verbose) - { - logf(" -- got it\n"); - } - - alarm(0); - alarmed = 0; - return (1); - } + if (Verbose) { + if (c == '\n') fputc( '\n', stderr ); + else if (c != '\r') fprintf( stderr, "%s", character(c) ); + } - for (n = 0; n < n_aborts; ++n) - { - if (s - temp >= (abort_len = strlen(abort_string[n])) && - strncmp(s - abort_len, abort_string[n], abort_len) == 0) - { - if (verbose) - { - logf(" -- failed\n"); - } - - alarm(0); - alarmed = 0; - exit_code = n + 4; - strcpy(fail_reason = fail_buffer, abort_string[n]); - return (0); - } - } + *s++ = c; if (!report_gathering) { @@ -1290,6 +1578,38 @@ register char *string; } } + if (s - temp >= len && + c == string[len - 1] && + strncmp(s - len, string, len) == 0) + { + if (verbose) + { + logf(" -- got it\n"); + } + + alarm(0); + alarmed = 0; + return (1); + } + + for (n = 0; n < n_aborts; ++n) + { + if (s - temp >= (abort_len = strlen(abort_string[n])) && + strncmp(s - abort_len, abort_string[n], abort_len) == 0) + { + if (verbose) + { + logf(" -- failed\n"); + } + + alarm(0); + alarmed = 0; + exit_code = n + 4; + strcpy(fail_reason = fail_buffer, abort_string[n]); + return (0); + } + } + if (s >= end) { strncpy (temp, s - minlen, minlen); @@ -1349,3 +1669,22 @@ usleep( usec ) /* returns 0 if ok, else -1 */ return select( 0, (long *)0, (long *)0, (long *)0, &delay ); } #endif + +void +pack_array (array, end) + char **array; /* The address of the array of string pointers */ + int end; /* The index of the next free entry before CLR_ */ +{ + int i, j; + + for (i = 0; i < end; i++) { + if (array[i] == NULL) { + for (j = i+1; j < end; ++j) + if (array[j] != NULL) + array[i++] = array[j]; + for (; i < end; ++i) + array[i] = NULL; + break; + } + } +} |