diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2015-03-23 03:49:03 +0000 |
---|---|---|
committer | <> | 2015-03-25 17:06:51 +0000 |
commit | fb040ea36cb8e2158ccd9100600652f94ae90af1 (patch) | |
tree | dba72a74e84a997c23fa0af7c07a4d831be2deb7 /src/files.c | |
parent | 8b74abeb02c01ddc768c465a826360cf33cec063 (diff) | |
download | nano-tarball-fb040ea36cb8e2158ccd9100600652f94ae90af1.tar.gz |
Imported from /home/lorry/working-area/delta_nano-tarball/nano-2.4.0.tar.gz.HEADnano-2.4.0master
Diffstat (limited to 'src/files.c')
-rw-r--r-- | src/files.c | 816 |
1 files changed, 652 insertions, 164 deletions
diff --git a/src/files.c b/src/files.c index c5b9d6a..bd07597 100644 --- a/src/files.c +++ b/src/files.c @@ -1,9 +1,9 @@ -/* $Id: files.c 4520 2010-11-12 06:23:14Z astyanax $ */ +/* $Id: files.c 5118 2015-02-15 16:28:08Z bens $ */ /************************************************************************** * files.c * * * * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * - * 2008, 2009 Free Software Foundation, Inc. * + * 2008, 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3, or (at your option) * @@ -31,6 +31,7 @@ #include <errno.h> #include <ctype.h> #include <pwd.h> +#include <libgen.h> /* Add an entry to the openfile openfilestruct. This should only be * called from open_buffer(). */ @@ -46,6 +47,8 @@ void make_new_buffer(void) } else { splice_opennode(openfile, make_new_opennode(), openfile->next); openfile = openfile->next; + /* More than one file open, show Close in help lines. */ + exitfunc->desc = close_tag; } /* Initialize the new buffer. */ @@ -77,8 +80,9 @@ void initialize_buffer(void) openfile->current_stat = NULL; openfile->undotop = NULL; openfile->current_undo = NULL; + openfile->lock_filename = NULL; #endif -#ifdef ENABLE_COLOR +#ifndef DISABLE_COLOR openfile->colorstrings = NULL; #endif } @@ -96,19 +100,229 @@ void initialize_buffer_text(void) openfile->edittop = openfile->fileage; openfile->current = openfile->fileage; -#ifdef ENABLE_COLOR +#ifndef DISABLE_COLOR openfile->fileage->multidata = NULL; #endif openfile->totsize = 0; } +#ifndef NANO_TINY +/* Actually write the lockfile. This function will ALWAYS annihilate + * any previous version of the file. We'll borrow INSECURE_BACKUP here + * to decide about lockfile paranoia here as well... + * + * Args: + * lockfilename: file name for lock + * origfilename: name of the file the lock is for + * modified: whether to set the modified bit in the file + * + * Returns: 1 on success, 0 on failure (but continue loading), -1 on + * failure and abort. */ +int write_lockfile(const char *lockfilename, const char *origfilename, bool modified) +{ + int cflags, fd; + FILE *filestream; + pid_t mypid; + uid_t myuid; + struct passwd *mypwuid; + struct stat fileinfo; + char *lockdata = charalloc(1024); + char myhostname[32]; + ssize_t lockdatalen = 1024; + ssize_t wroteamt; + + /* Run things which might fail first before we try and blow away the + * old state. */ + myuid = geteuid(); + if ((mypwuid = getpwuid(myuid)) == NULL) { + statusbar(_("Couldn't determine my identity for lock file (getpwuid() failed)")); + return -1; + } + mypid = getpid(); + + if (gethostname(myhostname, 31) < 0) { + statusbar(_("Couldn't determine hostname for lock file: %s"), strerror(errno)); + return -1; + } + + /* Check if the lock exists before we try to delete it...*/ + if (stat(lockfilename, &fileinfo) != -1) + if (delete_lockfile(lockfilename) < 0) + return -1; + + if (ISSET(INSECURE_BACKUP)) + cflags = O_WRONLY | O_CREAT | O_APPEND; + else + cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND; + + fd = open(lockfilename, cflags, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + /* Maybe we just don't have write access. Print an error message + and continue. */ + if (fd < 0) { + statusbar(_("Error writing lock file %s: %s"), lockfilename, + strerror(errno)); + return 0; + } + + /* Now we've got a safe file stream. If the previous open() call + * failed, this will return NULL. */ + filestream = fdopen(fd, "wb"); + + if (fd < 0 || filestream == NULL) { + statusbar(_("Error writing lock file %s: %s"), lockfilename, + strerror(errno)); + return -1; + } + + /* Okay, so at the moment we're following this state for how to + * store the lock data: + * + * byte 0 - 0x62 + * byte 1 - 0x30 + * bytes 2-12 - program name which created the lock + * bytes 24,25 - little endian store of creator program's PID + * (b24 = 256^0 column, b25 = 256^1 column) + * bytes 28-44 - username of who created the lock + * bytes 68-100 - hostname of where the lock was created + * bytes 108-876 - filename the lock is for + * byte 1007 - 0x55 if file is modified + * + * Looks like VIM also stores undo state in this file, so we're + * gonna have to figure out how to slap a 'OMG don't use recover on + * our lockfile' message in here... + * + * This is likely very wrong, so this is a WIP. */ + memset(lockdata, 0, lockdatalen); + lockdata[0] = 0x62; + lockdata[1] = 0x30; + lockdata[24] = mypid % 256; + lockdata[25] = mypid / 256; + snprintf(&lockdata[2], 11, "nano %s", VERSION); + strncpy(&lockdata[28], mypwuid->pw_name, 16); + strncpy(&lockdata[68], myhostname, 31); + strncpy(&lockdata[108], origfilename, 768); + if (modified == TRUE) + lockdata[1007] = 0x55; + + wroteamt = fwrite(lockdata, sizeof(char), lockdatalen, filestream); + if (wroteamt < lockdatalen) { + statusbar(_("Error writing lock file %s: %s"), + lockfilename, ferror(filestream)); + return -1; + } + +#ifdef DEBUG + fprintf(stderr, "In write_lockfile(), write successful (wrote %lu bytes)\n", (unsigned long)wroteamt); +#endif + + if (fclose(filestream) == EOF) { + statusbar(_("Error writing lock file %s: %s"), + lockfilename, strerror(errno)); + return -1; + } + + openfile->lock_filename = lockfilename; + + return 1; +} + +/* Less exciting, delete the lockfile. Return -1 if unsuccessful and + * complain on the statusbar, 1 otherwise. */ +int delete_lockfile(const char *lockfilename) +{ + if (unlink(lockfilename) < 0 && errno != ENOENT) { + statusbar(_("Error deleting lock file %s: %s"), lockfilename, + strerror(errno)); + return -1; + } + return 1; +} + +/* Deal with lockfiles. Return -1 on refusing to override the lockfile, + * and 1 on successfully creating it; 0 means we were not successful in + * creating the lockfile but we should continue to load the file and + * complain to the user. */ +int do_lockfile(const char *filename) +{ + char *lockdir = dirname((char *) mallocstrcpy(NULL, filename)); + char *lockbase = basename((char *) mallocstrcpy(NULL, filename)); + size_t lockfilesize = strlen(filename) + strlen(locking_prefix) + + strlen(locking_suffix) + 3; + char *lockfilename = charalloc(lockfilesize); + char *lockfiledir = NULL; + static char lockprog[11], lockuser[17]; + struct stat fileinfo; + int lockfd, lockpid; + + snprintf(lockfilename, lockfilesize, "%s/%s%s%s", lockdir, + locking_prefix, lockbase, locking_suffix); +#ifdef DEBUG + fprintf(stderr, "lock file name is %s\n", lockfilename); +#endif + if (stat(lockfilename, &fileinfo) != -1) { + ssize_t readtot = 0; + ssize_t readamt = 0; + char *lockbuf = charalloc(8192); + char *promptstr = charalloc(128); + int ans; + if ((lockfd = open(lockfilename, O_RDONLY)) < 0) { + statusbar(_("Error opening lock file %s: %s"), + lockfilename, strerror(errno)); + return -1; + } + do { + readamt = read(lockfd, &lockbuf[readtot], BUFSIZ); + readtot += readamt; + } while (readtot < 8192 && readamt > 0); + + if (readtot < 48) { + statusbar(_("Error reading lock file %s: Not enough data read"), + lockfilename); + return -1; + } + strncpy(lockprog, &lockbuf[2], 10); + lockpid = (unsigned char)lockbuf[25] * 256 + (unsigned char)lockbuf[24]; + strncpy(lockuser, &lockbuf[28], 16); +#ifdef DEBUG + fprintf(stderr, "lockpid = %d\n", lockpid); + fprintf(stderr, "program name which created this lock file should be %s\n", + lockprog); + fprintf(stderr, "user which created this lock file should be %s\n", + lockuser); +#endif + /* TRANSLATORS: The second %s is the name of the user, the third that of the editor. */ + sprintf(promptstr, _("File %s is being edited (by %s with %s, PID %d); continue?"), + filename, lockuser, lockprog, lockpid); + ans = do_yesno_prompt(FALSE, promptstr); + if (ans < 1) { + blank_statusbar(); + return -1; + } + } else { + lockfiledir = mallocstrcpy(NULL, lockfilename); + lockfiledir = dirname(lockfiledir); + if (stat(lockfiledir, &fileinfo) == -1) { + statusbar(_("Error writing lock file: Directory \'%s\' doesn't exist"), + lockfiledir); + return 0; + } + } + + + return write_lockfile(lockfilename, filename, FALSE); +} +#endif /* !NANO_TINY */ + /* If it's not "", filename is a file to open. We make a new buffer, if * necessary, and then open and read the file, if applicable. */ void open_buffer(const char *filename, bool undoable) { + bool quiet = FALSE; bool new_buffer = (openfile == NULL -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER || ISSET(MULTIBUFFER) #endif ); @@ -128,16 +342,34 @@ void open_buffer(const char *filename, bool undoable) } #endif - /* If the filename isn't blank, open the file. Otherwise, treat it - * as a new file. */ - rc = (filename[0] != '\0') ? open_file(filename, new_buffer, &f) : - -2; - /* If we're loading into a new buffer, add a new entry to * openfile. */ - if (new_buffer) + if (new_buffer) { make_new_buffer(); +#ifndef NANO_TINY + if (ISSET(LOCKING) && filename[0] != '\0') { + int lockstatus = do_lockfile(filename); + if (lockstatus < 0) { +#ifndef DISABLE_MULTIBUFFER + if (openfile->next) { + close_buffer(TRUE); + return; + } +#endif + } else if (lockstatus == 0) { + quiet = TRUE; + } + } +#endif + } + + + /* If the filename isn't blank, and we are not in NOREAD_MODE, + * open the file. Otherwise, treat it as a new file. */ + rc = (filename[0] != '\0' && !ISSET(NOREAD_MODE)) ? + open_file(filename, new_buffer, quiet, &f) : -2; + /* If we have a file, and we're loading into a new buffer, update * the filename. */ if (rc != -1 && new_buffer) @@ -164,7 +396,7 @@ void open_buffer(const char *filename, bool undoable) openfile->placewewant = 0; } -#ifdef ENABLE_COLOR +#ifndef DISABLE_COLOR /* If we're loading into a new buffer, update the colors to account * for it, if applicable. */ if (new_buffer) @@ -188,7 +420,7 @@ void replace_buffer(const char *filename) /* If the filename isn't blank, open the file. Otherwise, treat it * as a new file. */ - rc = (filename[0] != '\0') ? open_file(filename, TRUE, &f) : -2; + rc = (filename[0] != '\0') ? open_file(filename, TRUE, FALSE, &f) : -2; /* Reinitialize the text of the current buffer. */ free_filestruct(openfile->fileage); @@ -211,7 +443,7 @@ void display_buffer(void) /* Update the titlebar, since the filename may have changed. */ titlebar(NULL); -#ifdef ENABLE_COLOR +#ifndef DISABLE_COLOR /* Make sure we're using the buffer's associated colors, if * applicable. */ color_init(); @@ -221,17 +453,18 @@ void display_buffer(void) edit_refresh(); } -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER /* Switch to the next file buffer if next_buf is TRUE. Otherwise, * switch to the previous file buffer. */ -void switch_to_prevnext_buffer(bool next_buf) +void switch_to_prevnext_buffer(bool next_buf, bool quiet) { assert(openfile != NULL); /* If only one file buffer is open, indicate it on the statusbar and * get out. */ if (openfile == openfile->next) { - statusbar(_("No more open file buffers")); + if (quiet == FALSE) + statusbar(_("No more open file buffers")); return; } @@ -247,31 +480,35 @@ void switch_to_prevnext_buffer(bool next_buf) display_buffer(); /* Indicate the switch on the statusbar. */ - statusbar(_("Switched to %s"), - ((openfile->filename[0] == '\0') ? _("New Buffer") : - openfile->filename)); + if (quiet == FALSE) + statusbar(_("Switched to %s"), + ((openfile->filename[0] == '\0') ? _("New Buffer") : + openfile->filename)); #ifdef DEBUG dump_filestruct(openfile->current); #endif + display_main_list(); } /* Switch to the previous entry in the openfile filebuffer. */ void switch_to_prev_buffer_void(void) { - switch_to_prevnext_buffer(FALSE); + switch_to_prevnext_buffer(FALSE, FALSE); } /* Switch to the next entry in the openfile filebuffer. */ void switch_to_next_buffer_void(void) { - switch_to_prevnext_buffer(TRUE); + switch_to_prevnext_buffer(TRUE, FALSE); } /* Delete an entry from the openfile filebuffer, and switch to the one * after it. Return TRUE on success, or FALSE if there are no more open - * file buffers. */ -bool close_buffer(void) + * file buffers. + * quiet - should we print messages switching bufers + */ +bool close_buffer(bool quiet) { assert(openfile != NULL); @@ -279,24 +516,29 @@ bool close_buffer(void) if (openfile == openfile->next) return FALSE; +#ifndef DISABLE_HISTORIES + update_poshistory(openfile->filename, openfile->current->lineno, xplustabs() + 1); +#endif + /* Switch to the next file buffer. */ - switch_to_next_buffer_void(); + switch_to_prevnext_buffer(TRUE, quiet); /* Close the file buffer we had open before. */ unlink_opennode(openfile->prev); - display_main_list(); + /* If only one buffer is open now, show Exit in the help lines. */ + if (openfile == openfile->next) + exitfunc->desc = exit_tag; return TRUE; } -#endif /* ENABLE_MULTIBUFFER */ +#endif /* !DISABLE_MULTIBUFFER */ -/* A bit of a copy and paste from open_file(), is_file_writable() - * just checks whether the file is appendable as a quick - * permissions check, and we tend to err on the side of permissiveness - * (reporting TRUE when it might be wrong) to not fluster users - * editing on odd filesystems by printing incorrect warnings. - */ +/* A bit of a copy and paste from open_file(), is_file_writable() just + * checks whether the file is appendable as a quick permissions check, + * and we tend to err on the side of permissiveness (reporting TRUE when + * it might be wrong) to not fluster users editing on odd filesystems by + * printing incorrect warnings. */ int is_file_writable(const char *filename) { struct stat fileinfo, fileinfo2; @@ -305,7 +547,6 @@ int is_file_writable(const char *filename) char *full_filename; bool ans = TRUE; - if (ISSET(VIEW_MODE)) return TRUE; @@ -315,7 +556,7 @@ int is_file_writable(const char *filename) full_filename = get_full_path(filename); /* Okay, if we can't stat the path due to a component's - permissions, just try the relative one */ + permissions, just try the relative one. */ if (full_filename == NULL || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1)) full_filename = mallocstrcpy(NULL, filename); @@ -356,7 +597,7 @@ filestruct *read_line(char *buf, filestruct *prevnode, bool fileptr->data[buf_len - 1] = '\0'; #endif -#ifdef ENABLE_COLOR +#ifndef DISABLE_COLOR fileptr->multidata = NULL; #endif @@ -389,9 +630,9 @@ filestruct *read_line(char *buf, filestruct *prevnode, bool /* Read an open file into the current buffer. f should be set to the * open file, and filename should be set to the name of the file. - * undoable means do we want to create undo records to try and undo this. - * Will also attempt to check file writability if fd > 0 and checkwritable == TRUE - */ + * undoable means do we want to create undo records to try and undo + * this. Will also attempt to check file writability if fd > 0 and + * checkwritable == TRUE. */ void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable) { size_t num_lines = 0; @@ -674,9 +915,9 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkw * found". * * Return -2 if we say "New File", -1 if the file isn't opened, and the - * fd opened otherwise. The file might still have an error while reading + * fd opened otherwise. The file might still have an error while reading * with a 0 return value. *f is set to the opened file. */ -int open_file(const char *filename, bool newfie, FILE **f) +int open_file(const char *filename, bool newfie, bool quiet, FILE **f) { struct stat fileinfo, fileinfo2; int fd; @@ -688,22 +929,25 @@ int open_file(const char *filename, bool newfie, FILE **f) full_filename = get_full_path(filename); /* Okay, if we can't stat the path due to a component's - permissions, just try the relative one */ - if (full_filename == NULL + permissions, just try the relative one. */ + if (full_filename == NULL || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1)) full_filename = mallocstrcpy(NULL, filename); + if (stat(full_filename, &fileinfo) == -1) { - /* Well, maybe we can open the file even if the OS - says its not there */ + /* Well, maybe we can open the file even if the OS says it's + * not there. */ if ((fd = open(filename, O_RDONLY)) != -1) { - statusbar(_("Reading File")); + if (!quiet) + statusbar(_("Reading File")); free(full_filename); return 0; } if (newfie) { - statusbar(_("New File")); + if (!quiet) + statusbar(_("New File")); return -2; } statusbar(_("\"%s\" not found"), filename); @@ -722,7 +966,7 @@ int open_file(const char *filename, bool newfie, FILE **f) statusbar(_("Error reading %s: %s"), filename, strerror(errno)); beep(); - return -1; + return -1; } else { /* The file is A-OK. Open it. */ *f = fdopen(fd, "rb"); @@ -800,8 +1044,7 @@ void do_insertfile( filestruct *edittop_save = openfile->edittop; size_t current_x_save = openfile->current_x; ssize_t current_y_save = openfile->current_y; - bool edittop_inside = FALSE, meta_key = FALSE, func_key = FALSE; - const sc *s; + bool edittop_inside = FALSE; #ifndef NANO_TINY bool right_side_up = FALSE, single_line = FALSE; #endif @@ -812,22 +1055,21 @@ void do_insertfile( #ifndef NANO_TINY if (execute) { msg = -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER ISSET(MULTIBUFFER) ? _("Command to execute in new buffer [from %s] ") : #endif _("Command to execute [from %s] "); - } else { -#endif + } else +#endif /* NANO_TINY */ + { msg = -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER ISSET(MULTIBUFFER) ? _("File to insert into new buffer [from %s] ") : #endif _("File to insert [from %s] "); -#ifndef NANO_TINY } -#endif i = do_prompt(TRUE, #ifndef DISABLE_TABCOMP @@ -837,8 +1079,7 @@ void do_insertfile( execute ? MEXTCMD : #endif MINSERTFILE, ans, - &meta_key, &func_key, -#ifndef NANO_TINY +#ifndef DISABLE_HISTORIES NULL, #endif edit_refresh, msg, @@ -853,7 +1094,7 @@ void do_insertfile( * filename or command begins with a newline (i.e. an encoded * null), treat it as though it's blank. */ if (i == -1 || ((i == -2 || *answer == '\n') -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER && !ISSET(MULTIBUFFER) #endif )) { @@ -861,32 +1102,29 @@ void do_insertfile( break; } else { size_t pww_save = openfile->placewewant; + functionptrtype func = func_from_key(&i); ans = mallocstrcpy(ans, answer); - s = get_shortcut(currmenu, &i, &meta_key, &func_key); - #ifndef NANO_TINY -#ifdef ENABLE_MULTIBUFFER - - if (s && s->scfunc == NEW_BUFFER_MSG) { +#ifndef DISABLE_MULTIBUFFER + if (func == new_buffer_void) { /* Don't allow toggling if we're in view mode. */ if (!ISSET(VIEW_MODE)) TOGGLE(MULTIBUFFER); + else + beep(); continue; - } else + } #endif - if (s && s->scfunc == EXT_CMD_MSG) { + if (func == flip_execute_void) { execute = !execute; continue; } -#ifndef DISABLE_BROWSER - else -#endif #endif /* !NANO_TINY */ #ifndef DISABLE_BROWSER - if (s && s->scfunc == TO_FILES_MSG) { + if (func == to_files_void) { char *tmp = do_browse_from(answer); if (tmp == NULL) @@ -903,28 +1141,28 @@ void do_insertfile( /* If we don't have a file yet, go back to the statusbar * prompt. */ if (i != 0 -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER && (i != -2 || !ISSET(MULTIBUFFER)) #endif ) continue; #ifndef NANO_TINY - /* Keep track of whether the mark begins inside the - * partition and will need adjustment. */ - if (openfile->mark_set) { - filestruct *top, *bot; - size_t top_x, bot_x; + /* Keep track of whether the mark begins inside the + * partition and will need adjustment. */ + if (openfile->mark_set) { + filestruct *top, *bot; + size_t top_x, bot_x; - mark_order((const filestruct **)&top, &top_x, + mark_order((const filestruct **)&top, &top_x, (const filestruct **)&bot, &bot_x, &right_side_up); - single_line = (top == bot); - } + single_line = (top == bot); + } #endif -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER if (!ISSET(MULTIBUFFER)) { #endif /* If we're not inserting into a new buffer, partition @@ -937,7 +1175,7 @@ void do_insertfile( openfile->current_x); edittop_inside = (openfile->edittop == openfile->fileage); -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER } #endif @@ -948,7 +1186,7 @@ void do_insertfile( #ifndef NANO_TINY if (execute) { -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER if (ISSET(MULTIBUFFER)) /* Open a blank buffer. */ open_buffer("", FALSE); @@ -957,7 +1195,7 @@ void do_insertfile( /* Save the command's output in the current buffer. */ execute_command(answer); -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER if (ISSET(MULTIBUFFER)) { /* Move back to the beginning of the first line of * the buffer. */ @@ -980,12 +1218,20 @@ void do_insertfile( } #endif -#ifdef ENABLE_MULTIBUFFER - if (ISSET(MULTIBUFFER)) +#if !defined(DISABLE_MULTIBUFFER) && !defined(DISABLE_HISTORIES) + if (ISSET(MULTIBUFFER)) { /* Update the screen to account for the current * buffer. */ display_buffer(); - else + + ssize_t savedposline, savedposcol; + if (ISSET(POS_HISTORY) && +#ifndef NANO_TINY + !execute && +#endif + check_poshistory(answer, &savedposline, &savedposcol)) + do_gotolinecolumn(savedposline, savedposcol, FALSE, FALSE, FALSE, FALSE); + } else #endif { filestruct *top_save = openfile->fileage; @@ -1056,8 +1302,6 @@ void do_insertfile( break; } } - shortcut_init(FALSE); - free(ans); } @@ -1066,13 +1310,12 @@ void do_insertfile( * allow inserting a file into a new buffer. */ void do_insertfile_void(void) { - if (ISSET(RESTRICTED)) { - nano_disabled_msg(); + nano_disabled_msg(); return; } -#ifdef ENABLE_MULTIBUFFER +#ifndef DISABLE_MULTIBUFFER if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER)) statusbar(_("Key invalid in non-multibuffer mode")); else @@ -1100,7 +1343,7 @@ char *get_full_path(const char *origpath) bool path_only; if (origpath == NULL) - return NULL; + return NULL; /* Get the current directory. If it doesn't exist, back up and try * again until we get a directory that does, and use that as the @@ -1366,6 +1609,22 @@ bool check_operating_dir(const char *currpath, bool allow_tabcomp) #endif #ifndef NANO_TINY +/* Although this sucks, it sucks less than having a single 'my system is + * messed up and I'm blanket allowing insecure file writing operations'. */ +int prompt_failed_backupwrite(const char *filename) +{ + static int i; + static char *prevfile = NULL; /* What was the last file we were + * passed so we don't keep asking + * this? Though maybe we should... */ + if (prevfile == NULL || strcmp(filename, prevfile)) { + i = do_yesno_prompt(FALSE, + _("Failed to write backup file, continue saving? (Say N if unsure) ")); + prevfile = mallocstrcpy(prevfile, filename); + } + return i; +} + void init_backup_dir(void) { char *full_backup_dir; @@ -1387,7 +1646,7 @@ void init_backup_dir(void) backup_dir = full_backup_dir; } } -#endif +#endif /* !NANO_TINY */ /* Read from inn, write to out. We assume inn is opened for reading, * and out for writing. We return 0 on success, -1 on read error, or -2 @@ -1447,12 +1706,14 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type /* The file descriptor we use. */ mode_t original_umask = 0; /* Our umask, from when nano started. */ +#ifndef NANO_TINY bool realexists; /* The result of stat(). TRUE if the file exists, FALSE * otherwise. If name is a link that points nowhere, realexists * is FALSE. */ struct stat st; /* The status fields filled in by stat(). */ +#endif bool anyexists; /* The result of lstat(). The same as realexists, unless name * is a link. */ @@ -1464,7 +1725,6 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type /* The actual file, realname, we are writing to. */ char *tempname = NULL; /* The temp file name we write to on prepend. */ - int backup_cflags; assert(name != NULL); @@ -1502,17 +1762,18 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type goto cleanup_and_exit; } - /* Save the state of the file at the end of the symlink (if there is - * one). */ +#ifndef NANO_TINY + /* Check whether the file (at the end of the symlink) exists. */ realexists = (stat(realname, &st) != -1); -#ifndef NANO_TINY - /* if we have not stat()d this file before (say, the user just - * specified it interactively), stat and save the value - * or else we will chase null pointers when we do - * modtime checks, preserve file times, etc. during backup */ - if (openfile->current_stat == NULL && !tmp && realexists) + /* If we haven't stat()d this file before (say, the user just + * specified it interactively), stat and save the value now, + * or else we will chase null pointers when we do modtime checks, + * preserve file times, and so on, during backup. */ + if (openfile->current_stat == NULL && !tmp && realexists) { + openfile->current_stat = (struct stat *)nmalloc(sizeof(struct stat)); stat(realname, openfile->current_stat); + } /* We backup only if the backup toggle is set, the file isn't * temporary, and the file already exists. Furthermore, if we @@ -1527,6 +1788,7 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type char *backupname; struct utimbuf filetime; int copy_status; + int backup_cflags; /* Save the original file's access and modification times. */ filetime.actime = openfile->current_stat->st_atime; @@ -1582,10 +1844,11 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type _("Too many backup files?")); free(backuptemp); free(backupname); - /* If we can't write to the backup, DONT go on, since - whatever caused the backup file to fail (e.g. disk - full may well cause the real file write to fail, which - means we could lose both the backup and the original! */ + /* If we can't write to the backup, DON'T go on, since + * whatever caused the backup file to fail (e.g. disk + * full may well cause the real file write to fail, + * which means we could lose both the backup and the + * original! */ goto cleanup_and_exit; } else { free(backupname); @@ -1597,9 +1860,11 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type } /* First, unlink any existing backups. Next, open the backup - file with O_CREAT and O_EXCL. If it succeeds, we - have a file descriptor to a new backup file. */ + * file with O_CREAT and O_EXCL. If it succeeds, we have a file + * descriptor to a new backup file. */ if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) { + if (prompt_failed_backupwrite(backupname)) + goto skip_backup; statusbar(_("Error writing backup file %s: %s"), backupname, strerror(errno)); free(backupname); @@ -1608,13 +1873,13 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type if (ISSET(INSECURE_BACKUP)) backup_cflags = O_WRONLY | O_CREAT | O_APPEND; - else + else backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND; backup_fd = open(backupname, backup_cflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); /* Now we've got a safe file stream. If the previous open() - call failed, this will return NULL. */ + * call failed, this will return NULL. */ backup_file = fdopen(backup_fd, "wb"); if (backup_fd < 0 || backup_file == NULL) { @@ -1624,11 +1889,13 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type goto cleanup_and_exit; } - /* We shouldn't worry about chown()ing something if we're not - root, since it's likely to fail! */ + /* We shouldn't worry about chown()ing something if we're not + * root, since it's likely to fail! */ if (geteuid() == NANO_ROOT_UID && fchown(backup_fd, openfile->current_stat->st_uid, openfile->current_stat->st_gid) == -1 - && !ISSET(INSECURE_BACKUP)) { + && !ISSET(INSECURE_BACKUP)) { + if (prompt_failed_backupwrite(backupname)) + goto skip_backup; statusbar(_("Error writing backup file %s: %s"), backupname, strerror(errno)); free(backupname); @@ -1636,7 +1903,10 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type goto cleanup_and_exit; } - if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 && !ISSET(INSECURE_BACKUP)) { + if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 + && !ISSET(INSECURE_BACKUP)) { + if (prompt_failed_backupwrite(backupname)) + goto skip_backup; statusbar(_("Error writing backup file %s: %s"), backupname, strerror(errno)); free(backupname); @@ -1664,12 +1934,14 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type /* And set its metadata. */ if (utime(backupname, &filetime) == -1 && !ISSET(INSECURE_BACKUP)) { + if (prompt_failed_backupwrite(backupname)) + goto skip_backup; statusbar(_("Error writing backup file %s: %s"), backupname, strerror(errno)); - /* If we can't write to the backup, DONT go on, since - whatever caused the backup file to fail (e.g. disk - full may well cause the real file write to fail, which - means we could lose both the backup and the original! */ + /* If we can't write to the backup, DON'T go on, since + * whatever caused the backup file to fail (e.g. disk full + * may well cause the real file write to fail, which means + * we could lose both the backup and the original! */ goto cleanup_and_exit; } @@ -1874,7 +2146,7 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type if (!nonamechange) { openfile->filename = mallocstrcpy(openfile->filename, realname); -#ifdef ENABLE_COLOR +#ifndef DISABLE_COLOR /* We might have changed the filename, so update the colors * to account for it, and then make sure we're using * them. */ @@ -1894,7 +2166,8 @@ bool write_file(const char *name, FILE *f_open, bool tmp, append_type if (openfile->current_stat == NULL) openfile->current_stat = (struct stat *)nmalloc(sizeof(struct stat)); - stat(realname, openfile->current_stat); + if (!openfile->mark_set) + stat(realname, openfile->current_stat); #endif statusbar(P_("Wrote %lu line", "Wrote %lu lines", @@ -1960,23 +2233,24 @@ bool write_marked_file(const char *name, FILE *f_open, bool tmp, return retval; } + #endif /* !NANO_TINY */ /* Write the current file to disk. If the mark is on, write the current * marked selection to disk. If exiting is TRUE, write the file to disk * regardless of whether the mark is on, and without prompting if the - * TEMP_FILE flag is set. Return TRUE on success or FALSE on error. */ + * TEMP_FILE flag is set and the current file has a name. Return TRUE + * on success or FALSE on error. */ bool do_writeout(bool exiting) { int i; append_type append = OVERWRITE; char *ans; /* The last answer the user typed at the statusbar prompt. */ -#ifdef NANO_EXTRA +#ifndef DISABLE_EXTRA static bool did_credits = FALSE; #endif - bool retval = FALSE, meta_key = FALSE, func_key = FALSE; - const sc *s; + bool retval = FALSE; currmenu = MWRITEFILE; @@ -2030,8 +2304,7 @@ bool do_writeout(bool exiting) TRUE, #endif MWRITEFILE, ans, - &meta_key, &func_key, -#ifndef NANO_TINY +#ifndef DISABLE_HISTORIES NULL, #endif edit_refresh, "%s%s%s", msg, @@ -2049,11 +2322,12 @@ bool do_writeout(bool exiting) retval = FALSE; break; } else { + functionptrtype func = func_from_key(&i); + ans = mallocstrcpy(ans, answer); - s = get_shortcut(currmenu, &i, &meta_key, &func_key); #ifndef DISABLE_BROWSER - if (s && s->scfunc == TO_FILES_MSG) { + if (func == to_files_void) { char *tmp = do_browse_from(answer); if (tmp == NULL) @@ -2065,26 +2339,26 @@ bool do_writeout(bool exiting) } else #endif /* !DISABLE_BROWSER */ #ifndef NANO_TINY - if (s && s->scfunc == DOS_FORMAT_MSG) { + if (func == dos_format_void) { openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE : DOS_FILE; continue; - } else if (s && s->scfunc == MAC_FORMAT_MSG) { + } else if (func == mac_format_void) { openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE : MAC_FILE; continue; - } else if (s && s->scfunc == BACKUP_FILE_MSG) { + } else if (func == backup_file_void) { TOGGLE(BACKUP_FILE); continue; } else #endif /* !NANO_TINY */ - if (s && s->scfunc == PREPEND_MSG) { + if (func == prepend_void) { append = (append == PREPEND) ? OVERWRITE : PREPEND; continue; - } else if (s && s->scfunc == APPEND_MSG) { + } else if (func == append_void) { append = (append == APPEND) ? OVERWRITE : APPEND; continue; - } else if (s && s->scfunc == DO_HELP_VOID) { + } else if (func == do_help_void) { continue; } @@ -2092,7 +2366,7 @@ bool do_writeout(bool exiting) fprintf(stderr, "filename is %s\n", answer); #endif -#ifdef NANO_EXTRA +#ifndef DISABLE_EXTRA /* If the current file has been modified, we've pressed * Ctrl-X at the edit window to exit, we've pressed "y" at * the "Save modified buffer" prompt to save, we've entered @@ -2165,10 +2439,13 @@ bool do_writeout(bool exiting) } } #ifndef NANO_TINY - /* Complain if the file exists, the name hasn't changed, and the - stat information we had before does not match what we have now */ - else if (name_exists && openfile->current_stat && (openfile->current_stat->st_mtime < st.st_mtime || - openfile->current_stat->st_dev != st.st_dev || openfile->current_stat->st_ino != st.st_ino)) { + /* Complain if the file exists, the name hasn't changed, + * and the stat information we had before does not match + * what we have now. */ + else if (name_exists && openfile->current_stat && + (openfile->current_stat->st_mtime < st.st_mtime || + openfile->current_stat->st_dev != st.st_dev || + openfile->current_stat->st_ino != st.st_ino)) { i = do_yesno_prompt(FALSE, _("File was modified since you opened it, continue saving ? ")); if (i == 0 || i == -1) @@ -2275,7 +2552,7 @@ int diralphasort(const void *va, const void *vb) /* Standard function brain damage: We should be sorting * alphabetically and case-insensitively according to the current * locale, but there's no standard strcasecoll() function, so we - * have to use multibyte strcasecmp() instead, */ + * have to use multibyte strcasecmp() instead. */ return mbstrcasecmp(a, b); } @@ -2372,9 +2649,6 @@ char **cwd_tab_completion(const char *buf, bool allow_files, size_t *num_matches, size_t buf_len) { char *dirname = mallocstrcpy(NULL, buf), *filename; -#ifndef DISABLE_OPERATINGDIR - size_t dirnamelen; -#endif size_t filenamelen; char **matches = NULL; DIR *dir; @@ -2413,9 +2687,6 @@ char **cwd_tab_completion(const char *buf, bool allow_files, size_t return NULL; } -#ifndef DISABLE_OPERATINGDIR - dirnamelen = strlen(dirname); -#endif filenamelen = strlen(filename); while ((nextdir = readdir(dir)) != NULL) { @@ -2656,29 +2927,96 @@ const char *tail(const char *foo) return tmp; } -#if !defined(NANO_TINY) && defined(ENABLE_NANORC) -/* Return $HOME/.nano_history, or NULL if we can't find the home +#ifndef DISABLE_HISTORIES +/* Return the constructed dirfile path, or NULL if we can't find the home * directory. The string is dynamically allocated, and should be * freed. */ -char *histfilename(void) +char *construct_filename(const char *str) { - char *nanohist = NULL; + char *newstr = NULL; if (homedir != NULL) { size_t homelen = strlen(homedir); - nanohist = charalloc(homelen + 15); - strcpy(nanohist, homedir); - strcpy(nanohist + homelen, "/.nano_history"); + newstr = charalloc(homelen + strlen(str) + 1); + strcpy(newstr, homedir); + strcpy(newstr + homelen, str); } - return nanohist; + return newstr; +} + +char *histfilename(void) +{ + return construct_filename("/.nano/search_history"); +} + +/* Construct the legacy history filename. + * (Deprecate in 2.5, delete later.) */ +char *legacyhistfilename(void) +{ + return construct_filename("/.nano_history"); +} + +char *poshistfilename(void) +{ + return construct_filename("/.nano/filepos_history"); } -/* Load histories from ~/.nano_history. */ +void history_error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vfprintf(stderr, _(msg), ap); + va_end(ap); + + fprintf(stderr, _("\nPress Enter to continue\n")); + while (getchar() != '\n') + ; +} + +/* Now that we have more than one history file, let's just rely on a + * .nano dir for this stuff. Return 1 if the dir exists or was + * successfully created, and return 0 otherwise. */ +int check_dotnano(void) +{ + struct stat dirstat; + char *nanodir = construct_filename("/.nano"); + + if (stat(nanodir, &dirstat) == -1) { + if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { + history_error(N_("Unable to create directory %s: %s\n" + "It is required for saving/loading search history or cursor positions.\n"), + nanodir, strerror(errno)); + return 0; + } + } else if (!S_ISDIR(dirstat.st_mode)) { + history_error(N_("Path %s is not a directory and needs to be.\n" + "Nano will be unable to load or save search history or cursor positions.\n"), + nanodir); + return 0; + } + return 1; +} + +/* Load the search and replace histories from ~/.nano/search_history. */ void load_history(void) { char *nanohist = histfilename(); + char *legacyhist = legacyhistfilename(); + struct stat hstat; + + if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) { + if (rename(legacyhist, nanohist) == -1) + history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n" + "to the preferred location (%s) but encountered an error: %s"), + legacyhist, nanohist, strerror(errno)); + else + history_error(N_("Detected a legacy nano history file (%s) which I moved\n" + "to the preferred location (%s)\n(see the nano FAQ about this change)"), + legacyhist, nanohist); + } /* Assume do_rcfile() has reported a missing home directory. */ if (nanohist != NULL) { @@ -2688,12 +3026,8 @@ void load_history(void) if (errno != ENOENT) { /* Don't save history when we quit. */ UNSET(HISTORYLOG); - rcfile_error(N_("Error reading %s: %s"), nanohist, + history_error(N_("Error reading %s: %s"), nanohist, strerror(errno)); - fprintf(stderr, - _("\nPress Enter to continue starting nano.\n")); - while (getchar() != '\n') - ; } } else { /* Load a history list (first the search history, then the @@ -2718,8 +3052,11 @@ void load_history(void) fclose(hist); free(line); + if (search_history->prev != NULL) + last_search = mallocstrcpy(NULL, search_history->prev->data); } free(nanohist); + free(legacyhist); } } @@ -2745,7 +3082,7 @@ bool writehist(FILE *hist, filestruct *h) return TRUE; } -/* Save histories to ~/.nano_history. */ +/* Save the search and replace histories to ~/.nano/search_history. */ void save_history(void) { char *nanohist; @@ -2761,7 +3098,7 @@ void save_history(void) FILE *hist = fopen(nanohist, "wb"); if (hist == NULL) - rcfile_error(N_("Error writing %s: %s"), nanohist, + history_error(N_("Error writing %s: %s"), nanohist, strerror(errno)); else { /* Make sure no one else can read from or write to the @@ -2770,7 +3107,7 @@ void save_history(void) if (!writehist(hist, searchage) || !writehist(hist, replaceage)) - rcfile_error(N_("Error writing %s: %s"), nanohist, + history_error(N_("Error writing %s: %s"), nanohist, strerror(errno)); fclose(hist); @@ -2779,4 +3116,155 @@ void save_history(void) free(nanohist); } } -#endif /* !NANO_TINY && ENABLE_NANORC */ + +/* Save the recorded last file positions to ~/.nano/filepos_history. */ +void save_poshistory(void) +{ + char *poshist; + char *statusstr = NULL; + poshiststruct *posptr; + + poshist = poshistfilename(); + + if (poshist != NULL) { + FILE *hist = fopen(poshist, "wb"); + + if (hist == NULL) + history_error(N_("Error writing %s: %s"), poshist, + strerror(errno)); + else { + /* Make sure no one else can read from or write to the + * history file. */ + chmod(poshist, S_IRUSR | S_IWUSR); + + for (posptr = poshistory; posptr != NULL; posptr = posptr->next) { + statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4); + sprintf(statusstr, "%s %ld %ld\n", posptr->filename, (long)posptr->lineno, + (long)posptr->xno); + if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr)) + history_error(N_("Error writing %s: %s"), poshist, + strerror(errno)); + free(statusstr); + } + fclose(hist); + } + free(poshist); + } +} + +/* Update the recorded last file positions, given a filename, a line + * and a column. If no entry is found, add a new one at the end. */ +void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos) +{ + poshiststruct *posptr, *posprev = NULL; + char *fullpath = get_full_path(filename); + + if (fullpath == NULL) + return; + + for (posptr = poshistory; posptr != NULL; posptr = posptr->next) { + if (!strcmp(posptr->filename, fullpath)) { + posptr->lineno = lineno; + posptr->xno = xpos; + return; + } + posprev = posptr; + } + + /* Didn't find it, make a new node yo! */ + posptr = (poshiststruct *)nmalloc(sizeof(poshiststruct)); + posptr->filename = mallocstrcpy(NULL, fullpath); + posptr->lineno = lineno; + posptr->xno = xpos; + posptr->next = NULL; + + if (!poshistory) + poshistory = posptr; + else + posprev->next = posptr; + + free(fullpath); +} + +/* Check the recorded last file positions to see if the given file + * matches an existing entry. If so, return 1 and set line and column + * to the retrieved values. Otherwise, return 0. */ +int check_poshistory(const char *file, ssize_t *line, ssize_t *column) +{ + poshiststruct *posptr; + char *fullpath = get_full_path(file); + + if (fullpath == NULL) + return 0; + + for (posptr = poshistory; posptr != NULL; posptr = posptr->next) { + if (!strcmp(posptr->filename, fullpath)) { + *line = posptr->lineno; + *column = posptr->xno; + free(fullpath); + return 1; + } + } + free(fullpath); + return 0; +} + +/* Load the recorded file positions from ~/.nano/filepos_history. */ +void load_poshistory(void) +{ + char *nanohist = poshistfilename(); + + /* Assume do_rcfile() has reported a missing home directory. */ + if (nanohist != NULL) { + FILE *hist = fopen(nanohist, "rb"); + + if (hist == NULL) { + if (errno != ENOENT) { + /* Don't save history when we quit. */ + UNSET(POS_HISTORY); + history_error(N_("Error reading %s: %s"), nanohist, + strerror(errno)); + } + } else { + char *line = NULL, *lineptr, *xptr; + size_t buf_len = 0; + ssize_t read, lineno, xno; + poshiststruct *posptr; + + /* Read and parse each line, and put the data into the + * positions history structure. */ + while ((read = getline(&line, &buf_len, hist)) >= 0) { + if (read > 0 && line[read - 1] == '\n') { + read--; + line[read] = '\0'; + } + if (read > 0) + unsunder(line, read); + lineptr = parse_next_word(line); + xptr = parse_next_word(lineptr); + lineno = atoi(lineptr); + xno = atoi(xptr); + if (poshistory == NULL) { + poshistory = (poshiststruct *)nmalloc(sizeof(poshiststruct)); + poshistory->filename = mallocstrcpy(NULL, line); + poshistory->lineno = lineno; + poshistory->xno = xno; + poshistory->next = NULL; + } else { + for (posptr = poshistory; posptr->next != NULL; posptr = posptr->next) + ; + posptr->next = (poshiststruct *)nmalloc(sizeof(poshiststruct)); + posptr->next->filename = mallocstrcpy(NULL, line); + posptr->next->lineno = lineno; + posptr->next->xno = xno; + posptr->next->next = NULL; + } + } + + fclose(hist); + free(line); + } + free(nanohist); + } +} +#endif /* !DISABLE_HISTORIES */ |