/* | journal.c | | This file contains the routines for handling the journal file | for ae. | | $Header: /home/hugh/sources/aee/RCS/journal.c,v 1.38 2002/07/10 04:36:25 hugh Exp hugh $ */ /* | This file contains routines for performing journalling for aee and | xae. Journalling is writing lines and information about them to a | file when the cursor moves away from them (if they have been changed | during the time the cursor was on the line). | | The data structure ae_file_info contains the information about | locations in the file for where to find the information about the | current line, the information about the previous line, the info | about the next line, and the location of the actual text. The | length of the line (which is important!) is contained in the 'text' | structure for the line. | | The format that the information is written into the file is as | follows: | | +--+--+--+--+ | | | | | | previous line info location | +--+--+--+--+ | | +--+--+--+--+ | | | | | | next line info location | +--+--+--+--+ | | +--+--+--+--+ | | | | | | text location | +--+--+--+--+ | | +--+--+--+--+ | | | | | | text length (in bytes, including terminating NULL) | +--+--+--+--+ | */ /* | Also in this file are routines for managing information about what | journal files have been written, and the relationship between the | journal file and the file being journalled. This helps when | recovering from a disaster, and could be used to determine duplicate | edit sessions, or a failed previous session. | | The name of the file is ~/.aeeinfo, and there is a lock file named | ~/.aeeinfo.L. The format of ~/.aeeinfo is as follows: | | 32 30 /home/hugh/sources/aee/journal.c /tmp/hugh/journal/journal.c.rv | | where the first number is the length (decimal) if the full file name, | the second number is the length of the journal file name, followed by | the file name, and the name of the journal file. The fields are | separated by a single space (ASCII 32). | | */ /* | | Copyright (c) 1994, 1995, 1996, 1998 Hugh Mahon. | */ char *jrn_vers_str = "@(#) journal.c $Revision: 1.38 $"; #include "aee.h" #include /* | writes the contents of the line, updates the value stored in | memory of where the start of the line is stored in the file */ void write_journal(buffer, line) struct bufr *buffer; struct text *line; { int counter; int ret_val = 0; ret_val = line->file_info.line_location = lseek(buffer->journ_fd, 0, SEEK_END); for (counter = 0; (counter < line->line_length) && (ret_val != -1); counter += min(1024, (line->line_length - counter))) { ret_val = write(buffer->journ_fd, &(line->line[counter]), min(1024, (line->line_length - counter))); } update_journal_entry(buffer, line); line->changed = FALSE; } /* | update the information for a line that has already been written, or | write a line for the first after its values have been initialised | by journ_info_init */ void update_journal_entry(buffer, line) struct bufr *buffer; struct text *line; { int ret_val; if (line->file_info.info_location == NO_FURTHER_LINES) { journ_info_init(buffer, line); return ; } ret_val = lseek(buffer->journ_fd, line->file_info.info_location, SEEK_SET); ret_val |= write(buffer->journ_fd, (char *)&(line->file_info.prev_info), sizeof(unsigned long)); ret_val |= write(buffer->journ_fd, (char *)&(line->file_info.next_info), sizeof(unsigned long)); ret_val |= write(buffer->journ_fd, (char *)&(line->file_info.line_location), sizeof(unsigned long)); ret_val |= write(buffer->journ_fd, (char *)&(line->line_length), sizeof(int)); } /* | change journal info at first line if the first line is deleted | while editing */ void remove_journ_line(buffer, line) struct bufr *buffer; struct text *line; { if (line->prev_line == NULL) { /* | This was the first line, now the next line is. */ line->next_line->file_info.info_location = line->file_info.info_location; update_journal_entry(buffer, line->next_line); if (line->next_line->next_line != NULL) { line->next_line->next_line->file_info.prev_info = line->file_info.info_location; update_journal_entry(buffer, line->next_line->next_line); } } else if (line->next_line == NULL) { /* | This was the last line, now the prev line is. */ line->prev_line->file_info.next_info = NO_FURTHER_LINES; update_journal_entry(buffer, line->prev_line); /* | no point in writing to file since no way to | access current line now */ } else { /* | Need to allow previous line to see next line, | vice-versa. */ line->prev_line->file_info.next_info = line->next_line->file_info.info_location; line->next_line->file_info.prev_info = line->prev_line->file_info.info_location; update_journal_entry(buffer, line->next_line); update_journal_entry(buffer, line->prev_line); } } /* | initialize the location of where the information is to reside | (using lseek), initialize information for this line of where the | previous line is, set the information where to find this line for | the previous line, next line then | | update current line (write for the first time) | update the previous line (if prev_line != NULL) | update the next line (if next_line != NULL) | */ void journ_info_init(buffer, line) struct bufr *buffer; struct text *line; { int ret_val; ret_val = line->file_info.info_location = lseek(buffer->journ_fd, 0, SEEK_END); if (line->prev_line != NULL) { line->file_info.prev_info = line->prev_line->file_info.info_location; line->prev_line->file_info.next_info = line->file_info.info_location; } if (line->next_line != NULL) { line->file_info.next_info = line->next_line->file_info.info_location; line->next_line->file_info.prev_info = line->file_info.info_location; } else line->file_info.next_info = NO_FURTHER_LINES; update_journal_entry(buffer, line); if (line->prev_line != NULL) update_journal_entry(buffer, line->prev_line); if ((line->next_line != NULL) && (line->file_info.next_info != NO_FURTHER_LINES)) update_journal_entry(buffer, line->next_line); } /* | read the journal entry from the file */ void read_journal_entry(buffer, line) struct bufr *buffer; struct text *line; { int counter; lseek(buffer->journ_fd, line->file_info.info_location, SEEK_SET); read(buffer->journ_fd, (char *)&(line->file_info.prev_info), sizeof(unsigned long)); read(buffer->journ_fd, (char *)&(line->file_info.next_info), sizeof(unsigned long)); read(buffer->journ_fd, (char *)&(line->file_info.line_location), sizeof(unsigned long)); read(buffer->journ_fd, (char *)&(line->line_length), sizeof(unsigned int)); lseek(buffer->journ_fd, line->file_info.line_location, SEEK_SET); line->line = malloc(line->line_length); for (counter = 0; counter < line->line_length; counter += min(1024, (line->line_length - counter))) { read(buffer->journ_fd, &(line->line[counter]), min(1024, (line->line_length - counter))); } } /* | read the contents of the edited file from the journal file | read the file up to the first '\n' (line feed), which is where | the journal information begins | | This reads the contents of the edited file, plus sets the values | in the data structures to allow continued editing (it is recreated | within the editor, so why not?). */ int recover_from_journal(buffer, file_name) struct bufr *buffer; char *file_name; { int counter = 0; struct text *line; char temp; char done = FALSE; char name[1024]; /* name of file stored in journal file */ if ((buffer->journ_fd = open(file_name, O_RDONLY)) == -1) { /* | Unable to open journal file. */ return(1); } memset(name, 0, 1024); /* | get to first record in file (skip the file name) */ do { read(buffer->journ_fd, &temp, 1); if ((counter < 1024) && (temp != '\n')) name[counter] = temp; counter++; } while (temp != '\n'); line = buffer->first_line; /* | set up first entry */ line->file_info.info_location = counter; do { read_journal_entry(buffer, line); line->vert_len = (scanline(line, line->line_length) / COLS) + 1; line->max_length = line->line_length; if (line->file_info.next_info != NO_FURTHER_LINES) { line->next_line = txtalloc(); buffer->num_of_lines++; line->next_line->prev_line = line; line->next_line->next_line = NULL; line->next_line->line_number = line->line_number + 1; line = line->next_line; line->file_info.next_info = 0; line->file_info.info_location = line->prev_line->file_info.next_info; } else done = TRUE; } while (!done); curr_buff->pointer = buffer->first_line->line; close(buffer->journ_fd); change = TRUE; buffer->changed = TRUE; /* | open journal for further changes */ if (buffer->journalling) { if ((buffer->journ_fd = open(buffer->journal_file, O_WRONLY)) == -1) { wprintw(com_win, cant_opn_rcvr_fil_msg); buffer->journalling = FALSE; } } /* | Since the user can specify a journal name without specifying | the name of the file it journalled, use the name stored in | the journal file. */ if ((buffer->full_name == NULL) || (*buffer->full_name == (char) NULL)) { buffer->full_name = strdup(name); if ((buffer->full_name != NULL) && (*buffer->full_name != (char) NULL)) { buffer->file_name = ae_basename(buffer->full_name); if (strcmp(main_buffer_name, buffer->name)) buffer->name = strdup(buffer->file_name); } } /* | Success! */ return(0); } /* | journal database file routines */ static char *lock_file_name = NULL; static char *db_file_name = NULL; /* | write a lock file so that another process doesn't try to open | the file | | This is an atomic action since the open should fail if the file | already exists, thus it can't be opened if someone else has a | lock in place. */ int lock_journal_fd() { int counter = 0; int ret_val; if (lock_file_name == NULL) lock_file_name = resolve_name("~/.aeeinfo.L"); /* | At some point may want to put in a check for an old lock file. */ while (((ret_val = open(lock_file_name, (O_CREAT | O_EXCL), 0700)) == -1) && (counter < 3)) { sleep(1); counter++; } if (ret_val != -1) close(ret_val); return(ret_val); } /* | remove the lock file */ void unlock_journal_fd() { unlink(lock_file_name); } /* | free the memory used in the list of files being edited and their | journal files */ void free_db_list(list) struct journal_db *list; { if (list->next != NULL) free_db_list(list->next); free(list->journal_name); if (list->file_name != NULL) free(list->file_name); free(list); } /* | read the file ~/.aeeinfo (which contains the name of the file | being edited and the name of the journal file associated with it) */ struct journal_db * read_journal_db() { char buffer[4092]; char *tmp; int file_name_len; int journ_name_len; struct journal_db *top_of_list = NULL; struct journal_db *list = NULL; FILE *db_fp; if (lock_journal_fd() == -1) return(NULL); if (db_file_name == NULL) db_file_name = resolve_name("~/.aeeinfo"); db_fp = fopen(db_file_name, "r"); if (db_fp == NULL) { unlock_journal_fd(); return(NULL); } while ((tmp = fgets(buffer, 4092, db_fp)) != NULL) { if (list == NULL) top_of_list = list = (struct journal_db *) malloc(sizeof(struct journal_db)); else { list->next = (struct journal_db *) malloc(sizeof(struct journal_db)); list = list->next; } list->next = NULL; if ((*tmp == ' ') || (*tmp == '\t')) tmp = next_word(tmp); file_name_len = atoi(tmp); tmp = next_word(tmp); journ_name_len = atoi(tmp); if (file_name_len > 0) { tmp = next_word(tmp); list->file_name = (char *) malloc(file_name_len + 1); strncpy(list->file_name, tmp, file_name_len); list->file_name[file_name_len] = (char)NULL; } else list->file_name = NULL; tmp = next_word(tmp); list->journal_name = (char *) malloc(journ_name_len + 1); strncpy(list->journal_name, tmp, journ_name_len); list->journal_name[journ_name_len] = (char)NULL; } fclose(db_fp); return(top_of_list); } /* | Write the file ~/.aeeinfo . */ void write_db_file(list) struct journal_db *list; { struct journal_db *tmp; FILE *db_fp; db_fp = fopen(db_file_name, "w"); if (db_fp == NULL) return; for (tmp = list; (db_fp != NULL) && (tmp != NULL); tmp = tmp->next) { if (tmp->file_name != (char)NULL) { fprintf(db_fp, "%d %d %s %s\n", strlen(tmp->file_name), strlen(tmp->journal_name), tmp->file_name, tmp->journal_name); } else { fprintf(db_fp, "%d %d %s\n", 0, strlen(tmp->journal_name), tmp->journal_name); } } fclose(db_fp); unlock_journal_fd(); } /* | add an entry to the file ~/.aeeinfo (a new file being edited and | its associated journal file) */ void add_to_journal_db(buffer) struct bufr *buffer; { struct journal_db *top_of_list; struct journal_db *list; top_of_list = list = read_journal_db(); if (list == (struct journal_db *) -1) /* could not open lock file */ return; if (list == NULL) { top_of_list = list = (struct journal_db *) malloc(sizeof(struct journal_db)); } else { /* | Could check for duplicate file being edited here. */ while (list->next != NULL) list = list->next; list->next = (struct journal_db *) malloc(sizeof(struct journal_db)); list = list->next; } list->next = NULL; list->journal_name = strdup(buffer->journal_file); if (*buffer->full_name != (char)NULL) list->file_name = strdup(buffer->full_name); else list->file_name = NULL; write_db_file(top_of_list); free_db_list(top_of_list); } /* | remove an entry from the file ~/.aeeinfo (the editor is being | exited and it is time to delete the journal file, so remove the | reference) */ void rm_journal_db_entry(buffer) struct bufr *buffer; { struct journal_db *top_of_list; struct journal_db *list; struct journal_db *tmp; int changed = FALSE; top_of_list = list = read_journal_db(); if (list == (struct journal_db *) NULL) /* could not open db file */ { return; } if ((!strcmp(buffer->journal_file, top_of_list->journal_name)) && ((top_of_list->file_name == NULL) || ((top_of_list->file_name != NULL) && (!strcmp(buffer->full_name, top_of_list->file_name))))) { top_of_list = list->next; tmp = list; changed = TRUE; } else { while (((list->next != NULL) && (buffer->journal_file != NULL) && (buffer->full_name != NULL) && (!((list->next->file_name != NULL) && (!strcmp(buffer->full_name, list->next->file_name) && !strcmp(buffer->journal_file, list->next->journal_name))))) && (!((list->next->file_name == NULL) && !strcmp(buffer->journal_file, list->next->journal_name)))) { list = list->next; } if (list->next != NULL) { tmp = list->next; list->next = list->next->next; changed = TRUE; } } if (changed == TRUE) { if (top_of_list == NULL) /* no entries for file */ { unlink(db_file_name); unlock_journal_fd(); } else { write_db_file(top_of_list); } tmp->next = NULL; free_db_list(tmp); } else unlock_journal_fd(); if (top_of_list != NULL) free_db_list(top_of_list); } /* | check for the existence of a directory, and if it doesn't exist | create it (will recursively go through and create the directory | from the highest location where a directory does exist) */ int create_dir(name) char *name; { char *path; struct stat buf; int ret_val; if ((name == (char *)NULL) || (*name == (char) NULL)) return(-1); ret_val = stat(name, &buf); if (ret_val == -1) { path = ae_dirname(name); ret_val = create_dir(path); if (ret_val != -1) ret_val = mkdir(name, 0700); free(path); } return(ret_val); } /* | determine a name for a journal file and make sure no other journal | file of that name already exists (if one does, create a name based | on the current date and time) */ void journal_name(buffer, file_name) struct bufr *buffer; char *file_name; { char *temp, *buff, *name; int count; if (*file_name == (char)NULL) { struct tm *time_info; time_t t; int ret_val; /* | create a file name based on the date, e.g., | 950913221206.rv | Not a guarantee of uniqueness, but certainly not | likely to be repeated. */ file_name = (char *)malloc(14); t = time(NULL); time_info = gmtime(&t); ret_val = strftime(file_name, 13, "%y%m%d%H%M%S", time_info); } if ((journal_dir != NULL) && (*journal_dir != (char) NULL)) { name = ae_basename(file_name); buffer->journal_file = temp = xalloc(strlen(name) + 5 + strlen(journal_dir)); strcpy(temp, journal_dir); strcat(temp, "/"); strcat(temp, name); } if (buffer->journal_file == NULL) { buffer->journal_file = temp = xalloc(strlen(file_name) + 4); strcpy(temp, file_name); } buff = ae_basename(temp); temp = buff; if (strlen(temp) >= (MAX_NAME_LEN - 3)) { for (count = 1; count < (MAX_NAME_LEN - 3); count++) temp++; } else { while(*temp != (char) NULL) temp++; } copy_str(".rv", temp); } /* | create and initialize the journal file */ void open_journal_for_write(buffer) struct bufr *buffer; { int ret_val; int length; int counter; char *temp; struct tm *time_info; time_t t; if (((buffer->journ_fd = open(buffer->journal_file, (O_CREAT | O_EXCL | O_WRONLY), 0600)) == -1) && (errno == EEXIST)) { /* | file already exists */ buffer->journal_file = realloc(buffer->journal_file, (strlen(buffer->journal_file) + 17)); /* | find the last '/' */ temp = strrchr(buffer->journal_file, '/'); if (temp != NULL) temp++; else temp = buffer->journal_file; /* | create a file name based on the date, e.g., | 950913221206.rv | Not a guarantee of uniqueness, but certainly not | likely to be repeated. */ t = time(NULL); time_info = gmtime(&t); ret_val = strftime(temp, 16, "%y%m%d%H%M%S.rv", time_info); /* | For some extra paranoia, check this one, and append | a character (a-z) to get uniqueness. */ counter = 'a'; length = strlen (buffer->journal_file); while (((buffer->journ_fd = open(buffer->journal_file, (O_CREAT | O_EXCL | O_WRONLY), 0600)) == -1) && (errno == EEXIST) && (counter <= 'z')) { buffer->journal_file[length] = counter; buffer->journal_file[length + 1] = (char) NULL; counter++; } } if (buffer->journ_fd == -1) { wprintw(com_win, cant_opn_rcvr_fil_msg); buffer->journalling = FALSE; return; } add_to_journal_db(buffer); if (*buffer->full_name != (char) NULL) { ret_val = write(buffer->journ_fd, buffer->full_name, strlen(buffer->full_name)); } ret_val |= write(buffer->journ_fd, "\n", 1); journ_info_init(buffer, buffer->first_line); } /* | delete a journal file and delete its entry from the file ~/.aeeinfo */ void remove_journal_file(buffer) struct bufr *buffer; { close(buffer->journ_fd); unlink(buffer->journal_file); rm_journal_db_entry(buffer); }