From bf88d27adc040da95ee6a75b24ad26079cf10f38 Mon Sep 17 00:00:00 2001 From: Chris Allegretta Date: Tue, 1 Jan 2013 03:24:39 +0000 Subject: [PATCH] 2012-12-31 Chris Allegretta * src/*: Introduce (basic) vim-style file locks. Does not allow vim to recover our files, and doesn't yet support setting the file as modified; just lets a vim user know we're editing a file. Commands line "-G" or "--locking", nanorc option "locking". New functions src/files.c:do_lockfile(), write_lockfile(), and delete_lockfile(). git-svn-id: svn://svn.savannah.gnu.org/nano/trunk/nano@4549 35c25a1d-7b9e-4130-9fde-d3aeb78583b8 --- ChangeLog | 7 ++ configure.ac | 2 +- doc/man/nano.1 | 3 + doc/man/nanorc.5 | 3 + doc/syntax/nanorc.nanorc | 2 +- src/files.c | 211 ++++++++++++++++++++++++++++++++++++++- src/global.c | 5 + src/nano.c | 17 +++- src/nano.h | 5 +- src/proto.h | 4 + src/rcfile.c | 3 + 11 files changed, 252 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index b7f05133..5580ba55 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2012-12-31 Chris Allegretta + * src/*: Introduce (basic) vim-style file locks. Does not allow vim to recover + our files, and doesn't yet support setting the file as modified; just lets a + vim user know we're editing a file. Commands line "-G" or "--locking", nanorc + option "locking". New functions src/files.c:do_lockfile(), write_lockfile(), + and delete_lockfile(). + 2012-02-05 Chris Allegretta * src/*: Fix overlapping strings highlighting each other. new variables in edit_draw (slmatcharray, pbegin, paintok), new logic (with repeated setting od values in the diff --git a/configure.ac b/configure.ac index c590737c..33199a8a 100644 --- a/configure.ac +++ b/configure.ac @@ -20,7 +20,7 @@ # # $Id$ -AC_INIT([GNU nano], [2.3.1], [nano-devel@gnu.org], [nano]) +AC_INIT([GNU nano], [2.3.1-svn], [nano-devel@gnu.org], [nano]) AC_CONFIG_SRCDIR([src/nano.c]) AC_CANONICAL_TARGET([]) AM_INIT_AUTOMAKE diff --git a/doc/man/nano.1 b/doc/man/nano.1 index 37c6eb3f..ca6f6ca2 100644 --- a/doc/man/nano.1 +++ b/doc/man/nano.1 @@ -77,6 +77,9 @@ Convert typed tabs to spaces. .B \-F (\-\-multibuffer) Enable multiple file buffers, if available. .TP +.B \-G (\-\-locking) +Enable vim-style file locking when editing files. +.TP .B \-H (\-\-historylog) Log search and replace strings to \fI~/.nano_history\fP, so they can be retrieved in later sessions, if \fInanorc\fP support is available. diff --git a/doc/man/nanorc.5 b/doc/man/nanorc.5 index da33179c..27fc9ecc 100644 --- a/doc/man/nanorc.5 +++ b/doc/man/nanorc.5 @@ -102,6 +102,9 @@ default value is \-8. Enable \fI~/.nano_history\fP for saving and reading search/replace strings. .TP +.B set/unset locking +Enable vim-style lock-files for when editing files. +.TP .B set matchbrackets "\fIstring\fP" Set the opening and closing brackets that can be found by bracket searches. They cannot contain blank characters. The former set must diff --git a/doc/syntax/nanorc.nanorc b/doc/syntax/nanorc.nanorc index bda5f4f4..2c9f23a4 100644 --- a/doc/syntax/nanorc.nanorc +++ b/doc/syntax/nanorc.nanorc @@ -4,7 +4,7 @@ syntax "nanorc" "\.?nanorc$" ## Possible errors and parameters icolor brightwhite "^[[:space:]]*((un)?set|include|syntax|i?color).*$" ## Keywords -icolor brightgreen "^[[:space:]]*(set|unset)[[:space:]]+(allow_insecure_backup|autoindent|backup|backupdir|backwards|boldtext|brackets|casesensitive|const|cut|fill|historylog|matchbrackets|morespace|mouse|multibuffer|noconvert|nofollow|nohelp|nonewlines|nowrap|operatingdir|poslog|preserve|punct)\>" "^[[:space:]]*(set|unset)[[:space:]]+(quickblank|quotestr|rebinddelete|rebindkeypad|regexp|smarthome|smooth|softwrap|speller|suspend|suspendenable|tabsize|tabstospaces|tempfile|undo|view|whitespace|wordbounds)\>" +icolor brightgreen "^[[:space:]]*(set|unset)[[:space:]]+(allow_insecure_backup|autoindent|backup|backupdir|backwards|boldtext|brackets|casesensitive|const|cut|fill|historylog|matchbrackets|morespace|mouse|multibuffer|noconvert|nofollow|nohelp|nonewlines|nowrap|operatingdir|poslog|preserve|punct)\>" "^[[:space:]]*(set|unset)[[:space:]]+(quickblank|quotestr|rebinddelete|rebindkeypad|regexp|smarthome|smooth|softwrap|speller|suspend|suspendenable|tabsize|tabstospaces|tempfile|undo|view|whitespace|wordbounds|locking)\>" icolor green "^[[:space:]]*(set|unset|include|syntax|header|magic)\>" ## Colors icolor yellow "^[[:space:]]*i?color[[:space:]]*(bright)?(white|black|red|blue|green|yellow|magenta|cyan)?(,(white|black|red|blue|green|yellow|magenta|cyan))?\>" diff --git a/src/files.c b/src/files.c index 382339a6..4f3d6b22 100644 --- a/src/files.c +++ b/src/files.c @@ -31,6 +31,7 @@ #include #include #include +#include /* Add an entry to the openfile openfilestruct. This should only be * called from open_buffer(). */ @@ -77,6 +78,7 @@ void initialize_buffer(void) openfile->current_stat = NULL; openfile->undotop = NULL; openfile->current_undo = NULL; + openfile->lock_filename = NULL; #endif #ifdef ENABLE_COLOR openfile->colorstrings = NULL; @@ -103,6 +105,197 @@ void initialize_buffer_text(void) openfile->totsize = 0; } + +#ifndef NANO_TINY +/* Actyally write the lock file. This function will + ALWAYS annihilate any previous version of the file. + We'll borrow INSECURE_BACKUP here to decide about lock file + 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, -1 on failure + */ +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; + 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 hosttname for lock file: %s"), strerror(errno)); + return -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); + /* 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 1018 - 0x55 if file is modified + (TODO: set if 'modified' == TRUE) + + 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 + */ + null_at(&lockdata, lockdatalen); + lockdata[0] = 0x62; + lockdata[1] = 0x30; + lockdata[24] = mypid % 256; + lockdata[25] = mypid / 256; + snprintf(&lockdata[2], 10, "nano %s", VERSION); + strncpy(&lockdata[28], mypwuid->pw_name, 16); + strncpy(&lockdata[68], myhostname, 31); + strncpy(&lockdata[108], origfilename, 768); + + 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 %d bytes)\n", wroteamt); +#endif /* DEBUG */ + + 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 lock file. + Return -1 if successful and complain on the statusbar, 1 otherwite + */ +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 lock file, and 1 on successfully created the lockfile. + */ +int do_lockfile(const char *filename) +{ + char *lockdir = dirname((char *) mallocstrcpy(NULL, filename)); + char *lockbase = basename((char *) mallocstrcpy(NULL, filename)); + ssize_t lockfilesize = (sizeof (char *) * (strlen(filename) + + strlen(locking_prefix) + strlen(locking_suffix) + 3)); + char *lockfilename = nmalloc(lockfilesize); + char lockprog[12], lockuser[16]; + 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 /* DEBUG */ + if (stat(lockfilename, &fileinfo) != -1) { + ssize_t readtot = 0; + ssize_t readamt = 0; + char *lockbuf = nmalloc(8192); + char *promptstr = nmalloc(128); + int ans; + if ((lockfd = open(lockfilename, O_RDONLY)) < 0) { + statusbar(_("Error opening lockfile %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 lockfile %s: Not enough data read"), + lockfilename); + return -1; + } + strncpy(lockprog, &lockbuf[2], 10); + lockpid = lockbuf[25] * 256 + 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 /* DEBUG */ + sprintf(promptstr, "File being edited (by %s, PID %d, user %s), continue?", + lockprog, lockpid, lockuser); + ans = do_yesno_prompt(FALSE, promptstr); + if (ans < 1) { + blank_statusbar(); + return -1; + } + } + + 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) @@ -128,16 +321,16 @@ 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) make_new_buffer(); + /* 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 have a file, and we're loading into a new buffer, update * the filename. */ if (rc != -1 && new_buffer) @@ -697,6 +890,13 @@ int open_file(const char *filename, bool newfie, FILE **f) || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1)) full_filename = mallocstrcpy(NULL, filename); + +#ifndef NANO_TINY + if (ISSET(LOCKING)) + if (do_lockfile(full_filename) < 0) + return -1; +#endif + if (stat(full_filename, &fileinfo) == -1) { /* Well, maybe we can open the file even if the OS says its not there */ @@ -1990,6 +2190,7 @@ 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 diff --git a/src/global.c b/src/global.c index b8355db8..a54f6f92 100644 --- a/src/global.c +++ b/src/global.c @@ -130,6 +130,11 @@ ssize_t tabsize = -1; #ifndef NANO_TINY char *backup_dir = NULL; /* The directory where we store backup files. */ + +char *locking_prefix = "."; + /* Prefix of how to store the vim-style lock file */ +char *locking_suffix = ".swp"; + /* Suffix of the vim-style lock file */ #endif #ifndef DISABLE_OPERATINGDIR char *operating_dir = NULL; diff --git a/src/nano.c b/src/nano.c index 90c42491..ccd62786 100644 --- a/src/nano.c +++ b/src/nano.c @@ -520,6 +520,7 @@ openfilestruct *make_new_opennode(void) #ifndef NANO_TINY newnode->current_stat = NULL; newnode->last_action = OTHER; + newnode->lock_filename = NULL; #endif return newnode; @@ -845,6 +846,8 @@ void usage(void) #endif #ifdef ENABLE_NANORC #ifndef NANO_TINY + print_opt("-G", "--locking", + N_("Use (vim-style) lock files")); print_opt("-H", "--historylog", N_("Log & read search/replace string history")); #endif @@ -1058,6 +1061,12 @@ void do_exit(void) /* If the user chose not to save, or if the user chose to save and * the save succeeded, we're ready to exit. */ if (i == 0 || (i == 1 && do_writeout(TRUE))) { + +#ifndef NANO_TINY + if (ISSET(LOCKING) && openfile->lock_filename) + delete_lockfile(openfile->lock_filename); +#endif /* NANO_TINY */ + #ifdef ENABLE_MULTIBUFFER /* Exit only if there are no more open file buffers. */ if (!close_buffer()) @@ -2098,6 +2107,7 @@ int main(int argc, char **argv) {"backup", 0, NULL, 'B'}, {"backupdir", 1, NULL, 'C'}, {"tabstospaces", 0, NULL, 'E'}, + {"locking", 0, NULL, 'G'}, {"historylog", 0, NULL, 'H'}, {"noconvert", 0, NULL, 'N'}, {"poslog", 0, NULL, 'P'}, @@ -2146,11 +2156,11 @@ int main(int argc, char **argv) while ((optchr = #ifdef HAVE_GETOPT_LONG getopt_long(argc, argv, - "h?ABC:DEFHIKLNOPQ:RST:UVWY:abcdefgijklmo:pqr:s:tuvwxz$", + "h?ABC:DEFGHIKLNOPQ:RST:UVWY:abcdefgijklmo:pqr:s:tuvwxz$", long_options, NULL) #else getopt(argc, argv, - "h?ABC:DEFHIKLNOPQ:RST:UVWY:abcdefgijklmo:pqr:s:tuvwxz$") + "h?ABC:DEFGHIKLNOPQ:RST:UVWY:abcdefgijklmo:pqr:s:tuvwxz$") #endif ) != -1) { switch (optchr) { @@ -2188,6 +2198,9 @@ int main(int argc, char **argv) #endif #ifdef ENABLE_NANORC #ifndef NANO_TINY + case 'G': + SET(LOCKING); + break; case 'H': SET(HISTORYLOG); break; diff --git a/src/nano.h b/src/nano.h index 4600d5d5..28d1d042 100644 --- a/src/nano.h +++ b/src/nano.h @@ -375,6 +375,8 @@ typedef struct openfilestruct { undo *current_undo; /* The current (i.e. n ext) level of undo */ undo_type last_action; + const char *lock_filename; + /* The path of the lockfile, if we created one */ #endif #ifdef ENABLE_COLOR syntaxtype *syntax; @@ -510,7 +512,8 @@ enum QUIET, UNDOABLE, SOFTWRAP, - POS_HISTORY + POS_HISTORY, + LOCKING }; /* Flags for which menus in which a given function should be present */ diff --git a/src/proto.h b/src/proto.h index aeb409db..7971fbf8 100644 --- a/src/proto.h +++ b/src/proto.h @@ -85,6 +85,8 @@ extern ssize_t tabsize; #ifndef NANO_TINY extern char *backup_dir; +extern char *locking_prefix; +extern char *locking_suffix; #endif #ifndef DISABLE_OPERATINGDIR extern char *operating_dir; @@ -253,6 +255,7 @@ void do_cut_text_void(void); #ifndef NANO_TINY void do_copy_text(void); void do_cut_till_end(void); + #endif void do_uncut_text(void); @@ -293,6 +296,7 @@ bool check_operating_dir(const char *currpath, bool allow_tabcomp); #endif #ifndef NANO_TINY void init_backup_dir(void); +int delete_lockfile(const char *lockfilename); #endif int copy_file(FILE *inn, FILE *out); bool write_file(const char *name, FILE *f_open, bool tmp, append_type diff --git a/src/rcfile.c b/src/rcfile.c index 8c62cf5b..56eeb72d 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -41,6 +41,9 @@ static const rcoption rcopts[] = { #ifndef DISABLE_WRAPJUSTIFY {"fill", 0}, #endif +#ifndef NANO_TINY + {"locking", LOCKING}, +#endif #ifndef DISABLE_MOUSE {"mouse", USE_MOUSE}, #endif -- 2.39.5