#ifndef lint #ifdef sccs static char sccsid[] = "@(#)ps_impl.c 1.1 94/10/31"; #endif #endif /* * Copyright (c) 1986, 1987 by Sun Microsystems Inc. */ /* * Entity stream implementation for manager of two other streams. * * Conceptually, suppose we want the piece source to contain: * AB0CD12E345FGH67I8JK9LMN * and the original source contains: * ABCDEFGHIJKLMN * then the operation sequence: * replace start:2 stop_plus_one:2 with:"0" * replace start:5 stop_plus_one:5 with:"12" * etc * will generate a scratch source that looks like: * xx0xx12xx345xx67xx8xx9 * and the piece table will alternate with: * virtual pos:0, length:2, source:original, source_pos:0 * virtual pos:2, length:1, source:scratch, source_pos:sizeof(xx) * virtual pos:3, length:2, source:original, source_pos:2 * virtual pos:5, length:2, source:scratch, source_pos:2*sizeof(xx)+1 * etc * * It is important that a piece in the piece table represent a contiguous * run of entities in the original or scratch stream. Thus, if a break is * detected during the first read of a piece, a new piece must be created. * * 87Mar23: A major new feature is support for a maximum size that the * scratch stream is allowed to grow to. The basic design: * Most of the piece_stream code believes that the scratch stream grows * without bound. However, in reality, if the client sets a maximum size on * the scratch stream, the ops vector for the scratch stream is modified * so that the logical index space of [0..private->scratch_length) is * mapped to the physical index space of [0..private->scratch_max_len), * essentially by making all accesses to the logical indices be modulo * private->scratch_max_len. * Some special care must be taken with UNDO and secondary piece streams, * as these both can use logical indices to reference entities that are no * longer physically available. */ #include #include #include #include #include #include #include #include "sunwindow/sv_malloc.h" extern char *calloc(); extern char *malloc(); static Es_status ps_commit(); static Es_handle ps_destroy(), ps_scratch_destroy(); static caddr_t ps_get(); static Es_index ps_get_length(), ps_scratch_get_length(); static Es_index ps_get_position(), ps_scratch_get_position(); static Es_index ps_set_position(), ps_scratch_set_position(); static Es_index ps_read(), ps_scratch_read(); static Es_index ps_replace(), ps_scratch_replace(); static int ps_set(); static void make_current_valid(); static Es_index write_header_etc(); static struct es_ops ps_ops = { ps_commit, ps_destroy, ps_get, ps_get_length, ps_get_position, ps_set_position, ps_read, ps_replace, ps_set }; static int max_transcript_alert_displayed; /* default = 0 */ #define INVALIDATE_CURRENT(private) \ (private)->current = CURRENT_NULL #define SET_POSITION(private, to) \ INVALIDATE_CURRENT(private); (private)->position = (to) #define VALID_PIECE_INDEX(private, index) \ ((index < (private)->pieces.last_plus_one) && \ (PIECES_IN_TABLE(private)[index].pos != ES_INFINITY)) static char *wrap_msg = "\n\ *** Text is lost because the maximum edit log size has been exceeded. ***\n\n\n"; #ifdef DEBUG #define MAX_PIECE_LENGTH 100000 #define MAX_LENGTH 4000000 #endif static Es_handle ps_NEW(piece_count) int piece_count; { extern ft_object ft_create(); Es_handle esh = NEW(Es_object); register Piece_table private = NEW(struct piece_table_object); if (esh == NULL || private == NULL) goto AllocFailed; private->magic = PS_MAGIC; if (piece_count > 0) { private->pieces = FT_CLIENT_CREATE(piece_count, Piece_object); if (private->pieces.seq == NULL) goto AllocFailed; FT_CLEAR_ALL(private->pieces); } else private->pieces.last_plus_one = 0; esh->data = (caddr_t)private; esh->ops = &ps_ops; return(esh); AllocFailed: if (private) free((char *)private); if (esh) free((char *)esh); return((Es_handle)NULL); } extern Es_handle ps_create(client_data, original, scratch) caddr_t client_data; Es_handle original, scratch; { extern ft_object ft_create(); extern int ft_set(); Es_handle esh = ps_NEW(100); register Piece_table private; register Piece pieces; if (es_set_position(scratch, 0) != 0) { (void) fprintf(stderr, "ps_create: cannot reset scratch stream.\n"); return(NULL); } if (esh == NULL) goto AllocFailed; private = ABS_TO_REP(esh); SET_POSITION(private, 0); private->length = (original != ES_NULL) ? es_get_length(original) : 0; pieces = PIECES_IN_TABLE(private); if (private->length > 0) { pieces[0].pos = es_set_position(original, 0); PS_SET_ORIGINAL_SANDP(pieces[0], pieces[0].pos); #ifdef notdef The following fails because it does not match the state after a replace of everything by nothing. However, it is probably correct should the end_of_stream condition change to an empty piece at the end. } else { pieces[0].pos = 0; PS_SET_SCRATCH_SANDP(pieces[0], pieces[0].pos); #endif } pieces[0].length = private->length; private->original = original; private->scratch = scratch; private->last_write_plus_one = ES_INFINITY; private->rec_start = ES_INFINITY; private->rec_insert = ES_INFINITY; private->oldest_not_undone_mark = ES_INFINITY; private->rec_insert_len = 0; private->client_data = client_data; private->scratch_max_len = ES_INFINITY; /* scratch_length need not be valid, but must be < scratch_max_len */ private->scratch_length = 0; private->scratch_ops = (Es_ops)0; private->status = ES_SUCCESS; return(esh); AllocFailed: (void) fprintf(stderr, "ps_create: alloc failure.\n"); return(NULL); } /* ARGSUSED */ static Es_status ps_commit(esh) Es_handle esh; { return(ES_SUCCESS); } static Es_handle ps_destroy(esh) Es_handle esh; { register Piece_table private = ABS_TO_REP(esh); free((char *)esh); ft_destroy(&private->pieces); free((char *)private); return NULL; } static Es_handle ps_scratch_destroy(esh) Es_handle esh; { register Piece_table private = SCRATCH_TO_REP(esh); free((char *)esh->ops); esh->ops = private->scratch_ops; return(es_destroy(esh)); } static Es_index ps_get_length(esh) Es_handle esh; { register Piece_table private = ABS_TO_REP(esh); return (private->length); } static Es_index ps_scratch_get_length(esh) Es_handle esh; { register Piece_table private = SCRATCH_TO_REP(esh); return (private->scratch_length); } static Es_index ps_get_position(esh) Es_handle esh; { register Piece_table private = ABS_TO_REP(esh); return(private->position); } static Es_index ps_scratch_get_position(esh) Es_handle esh; { register Piece_table private = SCRATCH_TO_REP(esh); return(private->scratch_position); } static Es_index ps_set_position(esh, pos) Es_handle esh; register Es_index pos; { register Piece_table private = ABS_TO_REP(esh); register Piece pieces = PIECES_IN_TABLE(private); if (pos > private->length) { ASSUME(pos == ES_INFINITY || private->parent); pos = private->length; } else if (pos < pieces[0].pos) { pos = pieces[0].pos; if (pos == ES_INFINITY) /* Checks for empty source. */ pos = 0; } ASSUME(0 <= pos && pos <= MAX_LENGTH); /* Client's often lose track of the position, so check if setting to * current position to avoid invalidating caches unnecessarily. */ if (pos != private->position) { /* Try to maintain private->current */ if (private->current != CURRENT_NULL) { if ((pos < pieces[private->current].pos) || (pos >= pieces[private->current].pos + pieces[private->current].length)) INVALIDATE_CURRENT(private); } private->position = pos; } return (private->position); } static Es_index ps_scratch_set_position(esh, pos) Es_handle esh; register Es_index pos; { register Piece_table private = SCRATCH_TO_REP(esh); if (pos > private->scratch_length) pos = private->scratch_length; private->scratch_position = pos; pos = pos % private->scratch_max_len; pos = private->scratch_ops->set_position(esh, pos); ASSUME(pos == (private->scratch_position % private->scratch_max_len)); return(private->scratch_position); } #ifdef DEBUG static int ps_pieces_are_consistent(private) register Piece_table private; { register Piece pieces = PIECES_IN_TABLE(private); register Es_index pos = pieces[0].pos; register int current; ASSUME(pos == 0 || pos == ES_INFINITY || private->parent); for (current = 0; VALID_PIECE_INDEX(private, current); current++) { ASSUME(pos == pieces[current].pos); ASSUME(0 < pieces[current].length || (current == 0 && pos == 0)); ASSUME(pieces[current].length < MAX_PIECE_LENGTH); pos += pieces[current].length; ASSUME(pos <= private->length); ASSUME(PS_SANDP_POS(pieces[current]) < MAX_LENGTH); } return(!0); } #endif static Es_index ps_scratch_read(esh, len, bufp, resultp) Es_handle esh; u_int len, *resultp; register char *bufp; { register Piece_table private = SCRATCH_TO_REP(esh); register Es_index first_valid, last_plus_one; Es_index remainder; long int count_read; if (!SCRATCH_HAS_WRAPPED(private)) { private->scratch_position = private->scratch_ops->read(esh, len, bufp, resultp); } else { first_valid = SCRATCH_FIRST_VALID(private); last_plus_one = private->scratch_position + len; /* * scratch stream has wrapped around, so valid "logical" scratch * indices are [first_valid..scratch_length). * Watch for: * a) read of characters that no longer exist, and * b) read that is "split" around physical scratch end. * First, look to see if read begins at valid position. */ if (private->scratch_position < first_valid) { /* Invalid start, but may extend into valid positions. * However, caller does not know that start is invalid; only * way to communicate this is to read nothing this time. * Move position to start of valid range (which changes * private->scratch_position as a side-effect). */ (void) es_set_position(esh, first_valid); *resultp = 0; goto Return; } /* Second, look for "split" read. */ if ((private->scratch_position / private->scratch_max_len) != ((last_plus_one-1) / private->scratch_max_len)) { /* Split => read at end of scratch, then at start. */ remainder = private->scratch_max_len - private->scratch_ops->get_position(esh); (void) private->scratch_ops->read(esh, remainder, bufp, &count_read); (void) private->scratch_ops->set_position(esh, 0); (void) private->scratch_ops->read(esh, len-count_read, bufp+count_read, resultp); *resultp += count_read; } else { (void) private->scratch_ops->read(esh, len, bufp, resultp); } /* Update the scratch_position, and watch out for read that * ends exactly at multiple of scratch_max_len, because this * requires repositioning of physical scratch stream to make * succeeding calls work properly. */ private->scratch_position += *resultp; if ((private->scratch_position % private->scratch_max_len) == 0) { (void) private->scratch_ops->set_position(esh, 0); } } Return: ASSUME(private->scratch_ops->get_position(esh) == (private->scratch_position % private->scratch_max_len)); ASSUME(ps_pieces_are_consistent(private)); return(private->scratch_position); } static Es_index ps_scratch_replace(esh, last_plus_one, count, buf, count_used) Es_handle esh; Es_index last_plus_one; long int count, *count_used; char *buf; { register Piece_table private = SCRATCH_TO_REP(esh); register Es_index first_valid, max_lpo; Es_index remainder; long int count_replaced; if (last_plus_one > private->scratch_length) { last_plus_one = private->scratch_length; } max_lpo = private->scratch_position + count; if (last_plus_one > max_lpo) { max_lpo = last_plus_one; } ASSUME(last_plus_one >= private->scratch_position); if (!SCRATCH_HAS_WRAPPED(private) && (max_lpo <= private->scratch_max_len) ) { private->scratch_position = private->scratch_ops->replace(esh, last_plus_one, count, buf, count_used); private->scratch_length = private->scratch_ops->get_length(esh); } else { first_valid = (SCRATCH_HAS_WRAPPED(private)) ? SCRATCH_FIRST_VALID(private) : 0; *count_used = count; /* * Either scratch stream has wrapped around, or it is about to * wrap around. In either case, the valid "logical" * scratch indices are [first_valid..scratch_length). * Bytes that vanish into hole at start of scratch stream are * treated as used. * Watch for: * a) range of characters that no longer exist, and * b) "split" around physical scratch end. * First, look to see if replace begins at valid position. */ remainder = first_valid - private->scratch_position; if (remainder > 0) { /* Invalid start, but may extend into valid positions. * Move position to start of valid range (which changes * private->scratch_position as a side-effect). */ (void) es_set_position(esh, first_valid); if (last_plus_one < first_valid) { /* Entire range to replace is invalid. */ goto Return; } else if (count > 0) { /* Discard any new bytes replacing invalid bytes. */ if (count > remainder) { count -= remainder; buf += remainder; } else { count = 0; } } } /* Second, look for "split" replace. */ if ((private->scratch_position / private->scratch_max_len) != ((max_lpo-1) / private->scratch_max_len) ) { /* Split => replace at end of scratch, then at start. * NOTE: caller guarantees that count > remainder */ remainder = private->scratch_max_len - private->scratch_ops->get_position(esh); ASSUME(count > remainder); (void) private->scratch_ops->replace( esh, private->scratch_max_len, remainder, buf, &count_replaced); (void) private->scratch_ops->set_position(esh, 0); (void) private->scratch_ops->replace( esh, count - remainder, count - remainder, buf + remainder, &count_replaced); } else { /* If here, then overwrite physical scratch after a wrap. */ (void) private->scratch_ops->replace( esh, (private->scratch_position % private->scratch_max_len) + count, count, buf, &count_replaced); } private->scratch_position += count; if (private->scratch_position > private->scratch_length) { private->scratch_length = private->scratch_position; } } Return: ASSUME(private->scratch_ops->get_position(esh) == (private->scratch_position % private->scratch_max_len)); return(private->scratch_position); } static Piece split_piece(pieces, current, delta) ft_handle pieces; register int current; register long int delta; /* Splits the piece_object pieces[current] in two with the second piece, the * new pieces[current+1], starting at pieces[current].pos+delta. * Returns a properly cast sequence, as old cached version may be invalidated * by the call to ft_shift_up. */ { register Piece old, new; register int is_scratch; register Es_index source_pos; ft_shift_up(pieces, current+1, current+2, 10); /* Can invalidate &pieces.seq[i] */ old = ((Piece)(pieces->seq)) + current; new = old + 1; is_scratch = PS_SANDP_SOURCE(old[0]); source_pos = PS_SANDP_POS(old[0]); new->pos = old->pos + delta; source_pos += delta; new->length = old->length - delta; old->length = delta; PS_SET_SANDP(new[0], source_pos, is_scratch); return((Piece)(pieces->seq)); } static Es_index ps_read(esh, len, bufp, resultp) Es_handle esh; u_int len, *resultp; register char *bufp; { #ifdef DEBUG_TRACE Es_index next, pos; char temp; pos = (ABS_TO_REP(esh))->position; next = ps_debug_read(esh, len, bufp, resultp); (void) fprintf(stdout, "ps_read at pos %d of %d chars got %d chars; %s %d ...\n", pos, len, *resultp, "next read at", next); #ifdef DEBUG_TRACE_CONTENTS temp = bufp[*resultp]; bufp[*resultp] = '\0'; (void) fprintf(stdout, "%s\n^^^\n", bufp); bufp[*resultp] = temp; #endif return(next); } static Es_index ps_debug_read(esh, len, bufp, resultp) Es_handle esh; u_int len, *resultp; register char *bufp; { #endif /* * "current" must be signed, because it can become -1 when deciding whether * to combine pieces containing invalid physical scratch indices. */ int read_count, save_current; Es_index next_pos, original_len; register int current, to_read; register long int delta; register Es_index current_pos; register Es_handle current_esh; register Piece pieces; register Piece_table private = ABS_TO_REP(esh); if (private->length - private->position < len) { len = private->length - private->position; } pieces = PIECES_IN_TABLE(private); *resultp = 0; if (private->current == CURRENT_NULL) { current = ft_bounding_index(&private->pieces, private->position); } else current = private->current; while (VALID_PIECE_INDEX(private, current) && len > 0) { delta = private->position - pieces[current].pos; ASSERT(*resultp == 0 || delta == 0); current_esh = (PS_SANDP_SOURCE(pieces[current]) ? private->scratch : private->original); current_pos = PS_SANDP_POS(pieces[current]) + delta; next_pos = es_set_position(current_esh, current_pos); ASSERT(next_pos == current_pos); to_read = pieces[current].length - delta; if (to_read > len) to_read = len; next_pos = es_read(current_esh, to_read, bufp, &read_count); /* BUG ALERT! If we ever support entities that are not bytes, * the following addition must have a * "* es_get(current_esh, ES_SIZE_OF_ENTITY)" */ bufp += read_count; len -= read_count; *resultp += read_count; private->position += read_count; /* zaps private->current */ if (read_count < to_read) { if (current_esh == private->original) { /* The original entity stream has holes in it, * and the initialization assumed it was contiguous, * so the pieces and length must be corrected. */ pieces = split_piece( &private->pieces, current, delta+read_count); current++; /* Compute the gap size */ delta = next_pos - (current_pos + read_count); ASSERT(pieces[current].length > delta); pieces[current].length -= delta; private->length -= delta; PS_SET_ORIGINAL_SANDP(pieces[current], next_pos); } else { /* The scratch entity stream has wrapped around, making a * hole from [0..scratch_first_valid). */ ASSUME(SCRATCH_HAS_WRAPPED(private) || (private->parent && SCRATCH_HAS_WRAPPED(ABS_TO_REP(private->parent)) )); /* Caller needs to be told where next successful read * is, so search for piece containing valid scratch * indices (WARNING: there may not be one). */ for (save_current = current, current_pos = private->position; VALID_PIECE_INDEX(private, current); current++) { ASSERT(PS_SANDP_SOURCE(pieces[current])); delta = PS_SANDP_POS(pieces[current]); if (delta >= next_pos) { /* next_pos not referenced by pieces => go to start * this piece, which is first beyond next_pos */ next_pos = delta; } else if (next_pos >= delta+pieces[current].length) { continue; } private->position = (next_pos - delta) + pieces[current].pos; /* Allow uniform handling of current below. */ current++; break; } if (current_pos == private->position) private->position = es_get_length(esh); /* Although the pieces are correct it speeds up later * reads if "runs of now invalid scratch references" * are combined; however, only completely invalid * pieces can be combined (else the record headers will * be incorrectly included as part of the visible text). * -2 backs over terminating and partially valid pieces. */ current -= 2; if (save_current < current) { delta = PS_LAST_PLUS_ONE(pieces[current]); pieces[save_current].length = delta - pieces[save_current].pos; ft_shift_out(&private->pieces, save_current+1, current+1); } /* A read at the beginning of the scratch stream which * tries to read out of the start of the hole should * give the client (an appropriate part of) wrap_msg. * However, don't do this until AFTER finding out where * next valid position is, otherwise can return too * much text. */ for (current = 0, original_len = 0; VALID_PIECE_INDEX(private, current); current++) { if (PS_SANDP_SOURCE(pieces[current])) break; original_len = PS_LAST_PLUS_ONE(pieces[current]); } current = private->pieces.last_plus_one; INVALIDATE_CURRENT(private); ASSUME(original_len <= current_pos); read_count = strlen(wrap_msg); if (*resultp == 0 && current_pos < original_len + read_count) { FILE * console_fd; if (private->position < original_len + read_count) read_count = private->position - original_len; read_count -= current_pos - original_len; if (len < read_count) read_count = len; bcopy(wrap_msg+current_pos, bufp, read_count); *resultp = read_count; /* tell user that they are going to wrap */ /* Use the same flag for console error message */ if (max_transcript_alert_displayed != 1) { console_fd = fopen("/dev/console", "a"); fprintf(console_fd, "The text transcript in a Commands window filled up, and the oldest saved text will be erased from the top as more is typed at the bottom.\n"); fflush(console_fd); max_transcript_alert_displayed = 1; fclose(console_fd); } #ifdef notdef if (max_transcript_alert_displayed != 1) { Event alert_event; (void) alert_prompt( (Frame)0, &alert_event, ALERT_MESSAGE_STRINGS, "Text has been lost in a cmdtool transcript because", "the maximum edit log size has been exceeded.", 0, ALERT_BUTTON_YES, "Continue", ALERT_TRIGGER, ACTION_STOP, /* with trigger, yes or no bring it down */ 0); max_transcript_alert_displayed = 1; } #endif } } /* All of the above code is free and easy with local * variables because it expects to exit the loop here, so * it will need to be rewritten if this break is removed! */ break; } current++; } if (VALID_PIECE_INDEX(private, current)) { private->current = current; if (pieces[current].pos > private->position) private->current--; /* Short read advances current anyway, so undo the advance. */ } else INVALIDATE_CURRENT(private); ASSUME(ps_pieces_are_consistent(private)); return (private->position); } /* The routines ps_replace, ps_insert_pieces, and ps_undo_to_mark all perform * similar functions and a fix to one needs to be reflected in the others. */ static Es_index ps_replace(esh, last_plus_one, count, buf, count_used) Es_handle esh; Es_index last_plus_one; long int count, *count_used; char *buf; { #ifdef DEBUG_TRACE Es_index next, pos; char temp; pos = (ABS_TO_REP(esh))->position; next = ps_debug_replace(esh, last_plus_one, count, buf, count_used); (void) fprintf(stdout, "ps_replace [%d..%d) by %d chars (used %d); next pos %d ...\n", pos, last_plus_one, count, *count_used, "next read at", next); if (buf) { temp = buf[count]; buf[count] = '\0'; (void) fprintf(stdout, "%s\n^^^\n", buf); buf[count] = temp; } return(next); } static Es_index ps_debug_replace(esh, last_plus_one, count, buf, count_used) Es_handle esh; Es_index last_plus_one; long int count, *count_used; char *buf; { #endif /* Assume that the piece stream looks like: * O1O S1S O2O S2S S3S O3O O4O S4S * where XnX means n-th chunk in stream X, and X2X may actually * occur earlier in stream X than X1X. * The possible cases are: * 1) Replace an empty region, with subcases: * 1a) Region is at end of previous replace, hence it can be treated * as an extension of the previous replace, and the associated * scratch piece can simply be lengthened. (S4S) * 1b) Otherwise, a new piece must be created, and the piece that * contains the empty region must be split (XnX -> XnX S5S XmX), * unless the empty region is at the beginning of that piece * (XnX -> S5S XnX). * 2) Replace a region completely within one piece: * 2a) If within latest scratch region, (1a) applies. * 2b) If the region exactly matches the piece, re-cycle the piece to * point at the new contents. * 2c) Otherwise, (1b) applies. * 3) Replace a region spanning multiple pieces. * 3a) For partially enclosed pieces, (1) applies. * 3b) For completely enclosed pieces, delete them all, except * possibly one to use as in (2b) if (3a) does not occur. * * WARNING! Changes to the pieces can only be made AFTER calls to * es_replace(scratch, ...) succeed, unless those changes do not * break the assertions about the pieces OR are backed out in case * of es_replace() failing. */ Es_index scratch_length; Es_index es_temp, new_rec_insert; long int long_temp; int delete_pieces_length, replace_used; register int current, end_index; register long int delta; /* Has 2 meanings! */ register Es_handle scratch; register Piece pieces; register Piece_table private = ABS_TO_REP(esh); *count_used = 0; if (buf == NULL && count != 0) { private->status = ES_INVALID_ARGUMENTS; return(ES_CANNOT_SET); } if (private->parent != NULL) { private->status = ES_INVALID_HANDLE; return(ES_CANNOT_SET); } scratch = private->scratch; scratch_length = es_set_position(scratch, ES_INFINITY); new_rec_insert = ES_INFINITY; /* => extending current */ if (last_plus_one > private->length) { last_plus_one = private->length; } ASSUME(last_plus_one >= private->position); pieces = PIECES_IN_TABLE(private); if (private->length == 0) { /* Special case occurring after everything replaced by nothing. */ ASSUME(pieces[0].pos == ES_INFINITY); if (count == 0) { /* Nothing replaced by nothing => leave well enough alone! */ return(private->position); } pieces[current = 0].pos = 0; goto Append_Only; } delta = get_current_offset(private); current = private->current; if (last_plus_one > private->position) { /* Replace a NON-empty region */ if (delta != 0) { /* Split the current piece at the insert point. */ pieces = split_piece(&private->pieces, current, delta); current++; } if (last_plus_one > PS_LAST_PLUS_ONE(pieces[current])) { /* Replace region spanning multiple pieces */ end_index = ft_bounding_index(&private->pieces, last_plus_one-1); /* -1 ensures that pieces[end_index] < last_plus_one, * preventing creation of a 0-length piece below. */ ASSERT(VALID_PIECE_INDEX(private, end_index)); } else { /* Replace region within a single piece */ end_index = current; } if (last_plus_one < PS_LAST_PLUS_ONE(pieces[end_index])) { /* Split the end piece at the last_plus_one point */ pieces = split_piece(&private->pieces, end_index, last_plus_one-pieces[end_index].pos); } /* Replace region now exactly equals [current..end_index] pieces. * Make sure all es_replace(scratch, ...) succeed before * modifying private->... (including pieces) any further. */ new_rec_insert = write_header_etc(scratch, private, last_plus_one, (long)count, buf, count_used, &es_temp, &delete_pieces_length, current, end_index+1); if (new_rec_insert == ES_CANNOT_SET) { goto Error_Return; } if (count == 0) { current--; /* Make ft_shift_out & ft_add_delta work */ } /* Recycle current piece, so don't shift it out */ ft_shift_out(&private->pieces, current+1, end_index+1); /* Meaning 2: total change to length */ delta = count - delete_pieces_length; } else if (count == 0) { /* Empty region replaced by nothing => leave well enough alone! * Not catching this case creates an extra 0-length piece. */ return(private->position); } else { /* Replace an empty region */ int at_end_of_stream = PS_IS_AT_END(private, delta, current); if (private->position == private->last_write_plus_one) { es_temp = es_replace(scratch, ES_INFINITY, count, buf, count_used); if (es_temp == ES_CANNOT_SET) { goto Error_Return; } ASSUME(count == *count_used); if (private->rec_insert_len == 0) { /* Last replace was a delete, so no scratch piece exists */ /* Split the current piece at the insert point */ pieces = split_piece(&private->pieces, current, delta); if (delta != 0) { ASSUME(at_end_of_stream); current++; delta = 0; } PS_SET_SCRATCH_SANDP(pieces[current], private->rec_insert+sizeof(long_temp)); } else if (at_end_of_stream) { /* Last replace was at end of stream => don't back up */ } else { ASSERT(delta == 0 && current > 0); current--; delta = private->position - pieces[current].pos; } ASSERT(delta == pieces[current].length); delta = count; /* Meaning 2: total change to length */ private->rec_insert_len += delta; pieces[current].length += delta; /* Correct the insert length (this is very inefficient) */ long_temp = private->rec_insert_len; es_temp = es_set_position(scratch, private->rec_insert); ASSERT(es_temp == private->rec_insert); es_temp = es_replace(scratch, private->rec_insert+sizeof(long_temp), sizeof(long_temp), (char *)&long_temp, &replace_used); } else { if (delta != 0) { /* Split the current piece at the insert point */ pieces = split_piece(&private->pieces, current, delta); current++; if (at_end_of_stream) { /* Append at the very end of the stream will create * a zero length piece which is unnecessary, so we * detect this case and recycle the piece. */ goto Append_Only; } } /* Handle the delta == 0 case. Create new piece */ pieces = split_piece(&private->pieces, current, 0L); Append_Only: /* Make sure all es_replace(scratch, ...) succeed before * modifying private->... (including pieces) any further. */ new_rec_insert = write_header_etc(scratch, private, last_plus_one, count, buf, count_used, &es_temp, (int *)0, -1, -1); if (new_rec_insert == ES_CANNOT_SET) { /* Reverse all damaging changes to pieces */ if (private->length == 0) { pieces[0].pos = ES_INFINITY; } else if (pieces[current].length == 0) { ft_shift_out(&private->pieces, current, current+1); } goto Error_Return; } delta = count; /* Meaning 2: total change to length */ } } /* Update (if necessary) the fields tracking the insert record. * Also fix up the associated piece fields. */ if (new_rec_insert != ES_INFINITY) { private->rec_insert = new_rec_insert; private->rec_insert_len = count; private->rec_start = scratch_length; if (private->oldest_not_undone_mark == ES_INFINITY) private->oldest_not_undone_mark = scratch_length; ASSUME(count == *count_used); if (count != 0) { pieces[current].length = count; ASSERT(es_temp == private->rec_insert+sizeof(long_temp)); PS_SET_SCRATCH_SANDP(pieces[current], es_temp); } } /* Adjust position, etc. to reflect the overall replace */ ft_add_delta(private->pieces, current+1, delta); private->length += delta; SET_POSITION(private, private->position + count); private->last_write_plus_one = private->position; ASSUME(0 <= private->position && private->position <= MAX_LENGTH); ASSUME(ps_pieces_are_consistent(private)); return(private->position); Error_Return: private->status = (Es_status)es_get(scratch, ES_STATUS); /* Copy scratch status first in case attempt to "back out" * failed es_replace's modify the status. */ (void) es_set_position(scratch, scratch_length); (void) es_replace(scratch, ES_INFINITY, 0, NULL, count_used); INVALIDATE_CURRENT(private); ASSUME(ps_pieces_are_consistent(private)); return(ES_CANNOT_SET); } static Es_index write_record_header(esh, private, last_plus_one, dp_count) Es_handle esh; register Piece_table private; Es_index last_plus_one; int dp_count; { struct piece_record_header r_header; register Es_index result; int replace_used; r_header.pos_prev_rec = private->rec_start; r_header.flags = 0; r_header.start = private->position; r_header.stop_plus_one = last_plus_one; r_header.dp_count = dp_count; result = es_replace(esh, ES_INFINITY, sizeof(r_header), (char *)&r_header, &replace_used); return(result); } static int record_deleted_pieces(esh, private, pieces, first, last_plus_one, next) Es_handle esh; Piece_table private; Piece pieces; int first, last_plus_one; Es_index *next; { struct deleted_piece d_header; int dummy; register Piece current, stop_plus_one; register int result = 0; register Es_index replace_result; stop_plus_one = &pieces[last_plus_one]; for (current = &pieces[first]; current < stop_plus_one; current++) { d_header.source_and_pos = current->source_and_pos; result += (d_header.length = current->length); replace_result = es_replace(esh, ES_INFINITY, sizeof(d_header), (char *)&d_header, &dummy); if (replace_result == ES_CANNOT_SET) break; } *next = replace_result; return(result); } /* Returns the new value for private->rec_insert */ static Es_index write_header_etc(esh, private, last_plus_one, count, buf, count_used, contents_start, deleted_pieces_length, first_deleted, last_plus_one_deleted) register Es_handle esh; register Piece_table private; Es_index last_plus_one; long int count; long int *count_used; char *buf; Es_index *contents_start; int *deleted_pieces_length; int first_deleted, last_plus_one_deleted; { register Es_index result; Es_index es_temp; int replace_used; /* Write the record header (and possibly) deleted pieces. */ result = write_record_header(esh, private, last_plus_one, last_plus_one_deleted-first_deleted); if (result == ES_CANNOT_SET) return(ES_CANNOT_SET); if (first_deleted < last_plus_one_deleted) { *deleted_pieces_length = record_deleted_pieces(esh, private, PIECES_IN_TABLE(private), first_deleted, last_plus_one_deleted, &es_temp); if (es_temp == ES_CANNOT_SET) return(ES_CANNOT_SET); result = es_temp; } *contents_start = es_replace(esh, ES_INFINITY, sizeof(count), (char *)&count, &replace_used); if (*contents_start == ES_CANNOT_SET) return(ES_CANNOT_SET); /* Do the insert */ if (count != 0) { es_temp = es_replace(esh, ES_INFINITY, count, buf, count_used); if (es_temp == ES_CANNOT_SET) return(ES_CANNOT_SET); } return(result); } static Es_handle ps_pieces_for_span(esh, first, last_plus_one, to_recycle) Es_handle esh; Es_index first, last_plus_one; Es_handle to_recycle; { register Piece_table private = ABS_TO_REP(esh); register Piece result_pieces; register int is_scratch; register Es_index delta, source_pos; int first_index, last_index; Es_handle result = (Es_handle)0; Piece_table r_private; if (last_plus_one > private->length) last_plus_one = private->length; if (first >= last_plus_one) { goto Bad_Args; } ASSUME(allock()); first_index = ft_bounding_index(&private->pieces, first); last_index = ft_bounding_index(&private->pieces, last_plus_one-1); if (to_recycle) { result = to_recycle; r_private = ABS_TO_REP(result); ASSUME(r_private->magic == PS_MAGIC && r_private->parent == esh); if (r_private->pieces.last_plus_one <= (last_index-first_index)) { ft_destroy(&r_private->pieces); /* Zeros .last_plus_one */ } } else { result = ps_NEW(0); if (result == (Es_handle)0) goto Out_Of_Memory; r_private = ABS_TO_REP(result); r_private->parent = esh; r_private->original = private->original; r_private->scratch = private->scratch; r_private->last_write_plus_one = r_private->rec_insert = r_private->rec_start = ES_CANNOT_SET; r_private->rec_insert_len = -1; } if (r_private->pieces.last_plus_one == 0) { r_private->pieces = FT_CLIENT_CREATE(last_index-first_index+1, Piece_object); if (r_private->pieces.seq == (Es_index *)0) goto Out_Of_Memory; } FT_CLEAR_ALL(r_private->pieces); copy_pieces(&r_private->pieces, 0, &private->pieces, first_index, last_index+1); /* * Fix up the end pieces. */ result_pieces = &PIECES_IN_TABLE(r_private)[last_index-first_index]; result_pieces->length = last_plus_one - result_pieces->pos; result_pieces = &PIECES_IN_TABLE(r_private)[0]; delta = first - result_pieces->pos; if (delta != 0) { result_pieces->pos = first; is_scratch = PS_SANDP_SOURCE(*result_pieces); source_pos = PS_SANDP_POS(*result_pieces); source_pos += delta; result_pieces->length -= delta; PS_SET_SANDP(*result_pieces, source_pos, is_scratch); } SET_POSITION(r_private, first); r_private->length = last_plus_one; ASSUME(allock()); return(result); Out_Of_Memory: if (result) { es_destroy(result); result = (Es_handle)0; } Bad_Args: if (to_recycle) es_destroy(to_recycle); return(result); } static copy_pieces(to_table, to_index, from_table, first, last_plus_one) register ft_handle to_table, from_table; int to_index, first, last_plus_one; { register int sizeof_element = from_table->sizeof_element; ASSERT(to_table->sizeof_element == sizeof_element); bcopy((char *)from_table->seq + first*sizeof_element, (char *)to_table->seq + to_index*sizeof_element, (last_plus_one-first)*sizeof_element); } static int get_current_offset(private) register Piece_table private; { register Piece current; register int result; if (private->current == CURRENT_NULL) private->current = ft_bounding_index(&private->pieces, private->position); ASSERT(VALID_PIECE_INDEX(private, private->current)); current = &PIECES_IN_TABLE(private)[private->current]; result = private->position - current->pos; ASSUME(result < current->length || PS_IS_AT_END(private, result, private->current) ); return(result); } static void ps_insert_pieces(esh, to_insert) Es_handle esh, to_insert; /* * Much of this routine is stolen from ps_replace. * A special convention established by this routine is that if the scratch * stream has a replace record with start == stop_plus_one and a non-zero * deleted piece count, then it is a result of inserting pieces, rather than * characters. */ { ft_handle rep = (ft_handle) &(ABS_TO_REP(to_insert))->pieces; long int long_temp; int at_end_of_stream, replace_used; long int delta; int save_last_plus_one; Es_index scratch_length, es_temp; register Piece pieces; register int current, last_valid; register Piece_table private = ABS_TO_REP(esh); register Es_handle scratch = private->scratch; ASSERT(rep != FT_NULL); last_valid = ft_bounding_index(rep, ES_INFINITY-1); ASSERT(last_valid != rep->last_plus_one); /* Prepare the insertion area. */ pieces = PIECES_IN_TABLE(private); if (private->length == 0 && pieces[0].pos == ES_INFINITY) { /* Special case that occurs if replaced everything by nothing. */ current = 0; delta = 0; at_end_of_stream = 1; pieces[0].pos = 0; pieces[0].length = 0; PS_SET_SCRATCH_SANDP(pieces[0], 0); } else { INVALIDATE_CURRENT(private); delta = get_current_offset(private); current = private->current; if (delta == 0) { at_end_of_stream = 0; } else { at_end_of_stream = (delta == pieces[current].length); /* * Split the current piece at the insert point. * This creates a zero length piece iff at_end_of_stream. */ pieces = split_piece(&private->pieces, current, delta); current++; } } #ifdef DEBUG long_temp = pieces[current].pos; #endif /* Do the insert and associated bookkeeping */ ft_shift_up(&private->pieces, current, current+last_valid+1, last_valid+1); pieces = PIECES_IN_TABLE(private); copy_pieces(&private->pieces, current, rep, 0, last_valid+1); ASSERT(allock()); /* Offset inserted pieces so they match the insertion point. * BUG ALERT: need version of ft_add_delta that takes stop index, * but for now, monkey with the piece table itself. */ save_last_plus_one = private->pieces.last_plus_one; private->pieces.last_plus_one = current+last_valid+1; ft_add_delta(private->pieces, current, private->position - rep->seq[0]); private->pieces.last_plus_one = save_last_plus_one; ASSERT(long_temp == pieces[current+last_valid+1].pos); /* Write header info to scratch. */ scratch_length = es_set_position(scratch, ES_INFINITY); es_temp = write_record_header(scratch, private, private->position, last_valid+1); if (es_temp != ES_CANNOT_SET) { /* Can modify private->... iff es_replace succeeds */ private->rec_insert = es_temp; private->rec_start = scratch_length; if (private->oldest_not_undone_mark == ES_INFINITY) private->oldest_not_undone_mark = scratch_length; } delta = record_deleted_pieces( scratch, private, pieces, current, current+last_valid+1, &private->rec_insert); long_temp = 0; (void) es_replace(scratch, ES_INFINITY, sizeof(long_temp), (char *)&long_temp, &replace_used); ASSERT(allock()); /* Adjust position, etc. to reflect the overall replace */ if (at_end_of_stream) { /* Flush the extraneous zero length piece we created. */ ASSERT(pieces[current+last_valid+1].length == 0); pieces[current+last_valid+1].pos = ES_INFINITY; } else { ft_add_delta(private->pieces, current+last_valid+1, delta); } private->last_write_plus_one = ES_INFINITY; private->length += delta; SET_POSITION(private, private->position + delta); } static Es_index adjust_pos_after_edit(pos, start, delta) register Es_index pos, start, delta; { if (delta > 0) return((start <= pos) ? pos+delta : pos); else if (start < pos) return((start-delta < pos) ? pos+delta : start); else return(pos); } static void ps_undo_to_mark(esh, mark, notify_proc, notify_data) Es_handle esh; Es_index mark; int (*notify_proc)(); caddr_t notify_data; /* * The notify_proc gets called with the notify_data, the start of the affected * range, and the delta. Thus a negative delta means [start..start-delta) * contains the affected positions, while a positive delta indicates an * insertion of entities to fill the range [start..start+delta). */ { register Piece_table private = ABS_TO_REP(esh); register Es_handle scratch = private->scratch; register Piece pieces, this_piece; register Es_index current_pos, scratch_pos, save_pos = private->position; Es_index delta, mark_pos = mark; struct piece_record_header r_header; struct deleted_piece d_header; int current, i, read; long int insert_len; if (es_get_length(scratch) == 0) return; /* For a bounded scratch stream, the mark_pos may now be invalid and * need to be adjusted. The new value need not be exactly at a record * header (luckily, else all of the record headers would have to be * read twice). */ if (SCRATCH_HAS_WRAPPED(private) && mark_pos < SCRATCH_FIRST_VALID(private)) { mark_pos = SCRATCH_FIRST_VALID(private); } /* Read back the information from the scratch source and undo it */ for (; (private->rec_start != ES_INFINITY) && (private->rec_start >= mark_pos); private->rec_start = r_header.pos_prev_rec) { (void) es_set_position(scratch, private->rec_start); (void) es_read(scratch, sizeof(r_header), (char *)&r_header, &read); ASSUME(read == sizeof(r_header)); /* Check to see if piece is flagged as already undone. * If not, make sure it is flagged now. */ if (r_header.flags & PS_ALREADY_UNDONE) continue; r_header.flags |= PS_ALREADY_UNDONE; (void) es_set_position(scratch, private->rec_start); (void) es_replace(scratch, private->rec_start+sizeof(r_header), sizeof(r_header), (char *)&r_header, &read); ASSUME(read == sizeof(r_header)); if (private->oldest_not_undone_mark == private->rec_start) private->oldest_not_undone_mark = ES_INFINITY; current_pos = r_header.start; SET_POSITION(private, current_pos); if (private->length == 0 && PIECES_IN_TABLE(private)[0].pos == ES_INFINITY) { /* Special case occurs when nothing replaced everything. * BUG ALERT: later on, we depend on the [0].pos being left * as ES_INFINITY, else the ft_shift_up corrupts the * piece table. */ current = 0; delta = 0; } else { delta = get_current_offset(private); current = private->current; } if ((r_header.start == r_header.stop_plus_one) && (r_header.dp_count != 0)) { /* Remove the inserted pieces. * Since ps_replace does NOT coalesce pieces, a single * inserted piece may be turned into multiple pieces via * sequences of "type-in edit-chars-deleting type-in", and * UNDO of those sequences also does NOT coalesce the pieces, * so when we go to UNDO the original insert we must not rely * on a one-to-one mapping of the pieces. */ int piece_length, piece_count = 0; ASSERT(delta == 0); pieces = PIECES_IN_TABLE(private); this_piece = &pieces[current]; for (i = r_header.dp_count; i > 0; i--) { (void) es_read(scratch, sizeof(d_header), (char *)&d_header, &read); ASSERT(this_piece->pos == current_pos-delta); ASSERT(this_piece->source_and_pos == d_header.source_and_pos); delta -= d_header.length; for (piece_length = d_header.length; piece_length > 0; piece_count++) { ASSERT(this_piece->length <= piece_length); piece_length -= this_piece->length; this_piece++; } } ft_shift_out(&private->pieces, current, current+piece_count); ft_add_delta(private->pieces, current, delta); save_pos = adjust_pos_after_edit(save_pos, current_pos, delta); private->length += delta; if (notify_proc) { scratch_pos = es_get_position(scratch); notify_proc(notify_data, current_pos, delta); (void) es_set_position(scratch, scratch_pos); } } else { /* Put back the deleted pieces */ if (delta == 0) { ft_add_delta(private->pieces, current, (long)(r_header.stop_plus_one-r_header.start)); } else { ASSERT(PS_IS_AT_END(private, delta, current)); current++; } ft_shift_up(&private->pieces, current, (int)(current+r_header.dp_count), (int)r_header.dp_count); pieces = PIECES_IN_TABLE(private); this_piece = &pieces[current]; delta = r_header.stop_plus_one-current_pos; save_pos = adjust_pos_after_edit(save_pos, current_pos, delta); for (i = 0; i < r_header.dp_count; i++, this_piece++) { (void) es_read(scratch, sizeof(d_header), (char *)&d_header, &read); this_piece->pos = current_pos; this_piece->length = d_header.length; this_piece->source_and_pos = d_header.source_and_pos; current_pos += d_header.length; } ASSERT(current_pos == r_header.stop_plus_one); if (delta != 0) { /* 0 iff no deleted pieces. */ private->length += delta; if (notify_proc) { scratch_pos = es_get_position(scratch); notify_proc(notify_data, r_header.start, delta); (void) es_set_position(scratch, scratch_pos); } } } (void) es_read(scratch, sizeof(insert_len), (char *)&insert_len, &read); if (insert_len > 0) { /* * Remove the inserted text. * Note that the user sequence: type-in edit-char type-in ... * can cause this insert to span multiple pieces in the * piece table. */ current += r_header.dp_count; this_piece = &pieces[current]; for (delta = 0, i = 0; delta < insert_len; delta += this_piece->length, this_piece++) { ASSERT(this_piece->pos == current_pos+delta); ASSERT(this_piece->length <= insert_len-delta); i++; } ft_shift_out(&private->pieces, current, current+i); ft_add_delta(private->pieces, current, -insert_len); save_pos = adjust_pos_after_edit(save_pos, current_pos, -insert_len); private->length -= insert_len; if (notify_proc) notify_proc(notify_data, current_pos, -insert_len); } ASSUME(ps_pieces_are_consistent(private)); } (void) es_set_position(scratch, ES_INFINITY); SET_POSITION(private, save_pos); private->last_write_plus_one = ES_INFINITY; } static caddr_t ps_get(esh, attribute, va_alist) Es_handle esh; Es_attribute attribute; va_dcl { register Piece_table private = ABS_TO_REP(esh); Es_index first, last_plus_one; Es_handle pieces_for_span, to_recycle; va_list args; if ((private->magic != PS_MAGIC) && (attribute != ES_TYPE)) return((caddr_t)0); switch (attribute) { case ES_CLIENT_DATA: return(private->client_data); case ES_UNDO_MARK: private->last_write_plus_one = ES_INFINITY; /* +1 below is because 0 == ES_NULL_UNDO_MARK */ return((caddr_t)(es_get_length(private->scratch)+1)); case ES_HANDLE_FOR_SPAN: va_start(args); #ifdef lint first = (args ? 0 : 0); last_plus_one = 0; to_recycle = 0; #else first = va_arg(args, Es_index); last_plus_one = va_arg(args, Es_index); to_recycle = va_arg(args, Es_handle); #endif pieces_for_span = ps_pieces_for_span(esh, first, last_plus_one, to_recycle); va_end(args); return((caddr_t)pieces_for_span); case ES_HAS_EDITS: return((caddr_t)(private->oldest_not_undone_mark != ES_INFINITY)); case ES_PS_ORIGINAL: return((caddr_t)(private->original)); case ES_PS_SCRATCH: return((caddr_t)(private->scratch)); case ES_PS_SCRATCH_MAX_LEN: return((caddr_t)(private->scratch_max_len)); case ES_STATUS: return((caddr_t)(private->status)); case ES_SIZE_OF_ENTITY: return(es_get(private->original, ES_SIZE_OF_ENTITY)); case ES_TYPE: return((caddr_t)ES_TYPE_PIECE); default: return(0); } } static int ps_set(esh, attrs) Es_handle esh; caddr_t *attrs; { register Piece_table private = ABS_TO_REP(esh); int (*notify_proc)() = (int (*)())0; caddr_t notify_data = 0; Es_index undo_mark; Es_index first; Es_handle to_recycle; Es_status status_dummy = ES_SUCCESS; register Es_status *status; status = &status_dummy; if (private->magic != PS_MAGIC) *status = ES_INVALID_TYPE; for (; *attrs && (*status == ES_SUCCESS); attrs = attr_next(attrs)) { switch ((Es_attribute)*attrs) { case ES_CLIENT_DATA: private->client_data = attrs[1]; break; case ES_HANDLE_TO_INSERT: to_recycle = (Es_handle)LINT_CAST(attrs[1]); if (private->scratch_max_len == ES_INFINITY) { ps_insert_pieces(esh, to_recycle); } else { /* When scratch is bounded, copy contents, not pieces. */ *status = es_copy(to_recycle, esh, FALSE); } break; case ES_PS_ORIGINAL: /* Caller should destroy the old private->original iff * return value is ES_SUCCESS, allowing for caller to * recover in case of errors. */ to_recycle = (Es_handle)LINT_CAST(attrs[1]); first = es_get_position(private->original); if (to_recycle == ES_NULL) { *status = ES_INVALID_HANDLE; } else if (es_get_length(private->original) != es_get_length(to_recycle)) { *status = ES_INCONSISTENT_LENGTH; } else if (first != es_set_position(to_recycle, first)) { *status = ES_INCONSISTENT_POS; } else { private->original = to_recycle; } break; case ES_PS_SCRATCH_MAX_LEN: first = (Es_index)LINT_CAST(attrs[1]); if (first < SCRATCH_MIN_LEN || first < es_get_length(private->scratch)) { *status = ES_INCONSISTENT_LENGTH; } else if (first >= ES_INFINITY) { if (private->scratch_max_len != ES_INFINITY) { *status = ES_INCONSISTENT_LENGTH; } } else { if (private->scratch_max_len == ES_INFINITY) { es_set(private->scratch, ES_CLIENT_DATA, esh, 0); private->scratch_max_len = first; private->scratch_length = es_get_length(private->scratch); private->scratch_position = es_get_position(private->scratch); /* Modify the scratch ops vector. */ private->scratch_ops = private->scratch->ops; private->scratch->ops = (Es_ops)sv_malloc(sizeof(struct es_ops)); *private->scratch->ops = *private->scratch_ops; private->scratch->ops->destroy = ps_scratch_destroy; private->scratch->ops->get_length = ps_scratch_get_length; private->scratch->ops->get_position = ps_scratch_get_position; private->scratch->ops->set_position = ps_scratch_set_position; private->scratch->ops->read = ps_scratch_read; private->scratch->ops->replace = ps_scratch_replace; } } break; case ES_STATUS: private->status = (Es_status)LINT_CAST(attrs[1]); break; case ES_STATUS_PTR: status = (Es_status *)LINT_CAST(attrs[1]); *status = status_dummy; break; case ES_UNDO_MARK: /* -1 below is because 0 == ES_NULL_UNDO_MARK */ undo_mark = ((Es_index)LINT_CAST(attrs[1]))-1; ps_undo_to_mark(esh, undo_mark, notify_proc, notify_data); break; case ES_UNDO_NOTIFY_PAIR: notify_proc = (int (*)())LINT_CAST(attrs[1]); notify_data = attrs[2]; break; default: break; } } return((*status == ES_SUCCESS)); }