2000-06-19 04:22:15 +00:00
|
|
|
/**************************************************************************
|
2016-08-29 15:10:49 +00:00
|
|
|
* files.c -- This file is part of GNU nano. *
|
2000-06-19 04:22:15 +00:00
|
|
|
* *
|
2020-01-15 10:42:38 +00:00
|
|
|
* Copyright (C) 1999-2011, 2013-2020 Free Software Foundation, Inc. *
|
2020-03-11 10:52:15 +00:00
|
|
|
* Copyright (C) 2015-2020 Benno Schulenberg *
|
2016-08-29 13:14:18 +00:00
|
|
|
* *
|
2016-08-29 15:10:49 +00:00
|
|
|
* GNU nano 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 of the License, *
|
|
|
|
* or (at your option) any later version. *
|
2000-06-19 04:22:15 +00:00
|
|
|
* *
|
2016-08-29 15:10:49 +00:00
|
|
|
* GNU nano is distributed in the hope that it will be useful, *
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty *
|
|
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
|
|
* See the GNU General Public License for more details. *
|
2000-06-19 04:22:15 +00:00
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
2016-08-29 15:10:49 +00:00
|
|
|
* along with this program. If not, see http://www.gnu.org/licenses/. *
|
2000-06-19 04:22:15 +00:00
|
|
|
* *
|
|
|
|
**************************************************************************/
|
|
|
|
|
2020-06-20 10:09:31 +00:00
|
|
|
#include "prototypes.h"
|
2001-04-28 18:03:52 +00:00
|
|
|
|
2000-06-19 04:22:15 +00:00
|
|
|
#include <errno.h>
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <libgen.h>
|
2017-02-21 22:04:44 +00:00
|
|
|
#ifdef HAVE_PWD_H
|
2001-01-22 22:17:08 +00:00
|
|
|
#include <pwd.h>
|
2017-02-21 22:04:44 +00:00
|
|
|
#endif
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
2020-03-06 17:52:42 +00:00
|
|
|
#include <sys/wait.h>
|
|
|
|
|
2020-05-17 17:11:54 +00:00
|
|
|
#define RW_FOR_ALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
|
|
|
|
|
2017-04-19 14:31:27 +00:00
|
|
|
/* Add an item to the circular list of openfile structs. */
|
2005-07-08 20:09:16 +00:00
|
|
|
void make_new_buffer(void)
|
2005-07-08 02:47:05 +00:00
|
|
|
{
|
2018-10-30 18:34:03 +00:00
|
|
|
openfilestruct *newnode = nmalloc(sizeof(openfilestruct));
|
2016-02-25 18:58:17 +00:00
|
|
|
|
2019-04-22 17:38:19 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile == NULL) {
|
|
|
|
/* Make the first open file the only element in the list. */
|
|
|
|
newnode->prev = newnode;
|
|
|
|
newnode->next = newnode;
|
2019-04-22 17:38:19 +00:00
|
|
|
|
2019-04-22 17:45:42 +00:00
|
|
|
startfile = newnode;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
|
|
|
/* Add the new open file after the current one in the list. */
|
|
|
|
newnode->prev = openfile;
|
|
|
|
newnode->next = openfile->next;
|
|
|
|
openfile->next->prev = newnode;
|
|
|
|
openfile->next = newnode;
|
2019-04-22 17:38:19 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* There is more than one file open: show "Close" in help lines. */
|
|
|
|
exitfunc->desc = close_tag;
|
|
|
|
more_than_one = !inhelp || more_than_one;
|
|
|
|
}
|
2019-04-22 17:38:19 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Make the new buffer the current one, and start initializing it. */
|
|
|
|
openfile = newnode;
|
2017-04-19 14:31:27 +00:00
|
|
|
|
2019-10-13 10:24:27 +00:00
|
|
|
openfile->filename = copy_of("");
|
2002-03-28 01:59:34 +00:00
|
|
|
|
2019-05-16 14:14:15 +00:00
|
|
|
openfile->filetop = make_new_node(NULL);
|
2019-10-13 10:24:27 +00:00
|
|
|
openfile->filetop->data = copy_of("");
|
2019-05-16 14:14:15 +00:00
|
|
|
openfile->filebot = openfile->filetop;
|
2002-06-13 00:40:19 +00:00
|
|
|
|
2019-05-16 14:14:15 +00:00
|
|
|
openfile->current = openfile->filetop;
|
|
|
|
openfile->current_x = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->placewewant = 0;
|
|
|
|
openfile->current_y = 0;
|
2002-06-13 00:40:19 +00:00
|
|
|
|
2019-05-16 14:14:15 +00:00
|
|
|
openfile->edittop = openfile->filetop;
|
|
|
|
openfile->firstcolumn = 0;
|
|
|
|
|
|
|
|
openfile->totsize = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->modified = FALSE;
|
2019-04-21 15:31:29 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
openfile->spillage_line = NULL;
|
|
|
|
#endif
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
2005-07-12 20:09:24 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->fmt = NIX_FILE;
|
2002-04-22 23:52:34 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->undotop = NULL;
|
|
|
|
openfile->current_undo = NULL;
|
|
|
|
openfile->last_saved = NULL;
|
|
|
|
openfile->last_action = OTHER;
|
2015-07-10 17:25:51 +00:00
|
|
|
|
2020-05-28 12:31:15 +00:00
|
|
|
openfile->statinfo = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->lock_filename = NULL;
|
2005-07-08 02:47:05 +00:00
|
|
|
#endif
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->syntax = NULL;
|
2005-07-13 20:18:46 +00:00
|
|
|
#endif
|
2005-07-08 02:47:05 +00:00
|
|
|
}
|
2000-06-19 04:22:15 +00:00
|
|
|
|
2013-01-01 03:24:39 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-02-09 19:10:28 +00:00
|
|
|
/* Delete the lockfile. Return TRUE on success, and FALSE otherwise. */
|
2020-02-09 13:24:26 +00:00
|
|
|
bool delete_lockfile(const char *lockfilename)
|
2020-02-02 11:14:32 +00:00
|
|
|
{
|
|
|
|
if (unlink(lockfilename) < 0 && errno != ENOENT) {
|
|
|
|
statusline(MILD, _("Error deleting lock file %s: %s"),
|
|
|
|
lockfilename, strerror(errno));
|
2020-02-09 13:24:26 +00:00
|
|
|
return FALSE;
|
|
|
|
} else
|
|
|
|
return TRUE;
|
2020-02-02 11:14:32 +00:00
|
|
|
}
|
|
|
|
|
2020-02-10 08:52:41 +00:00
|
|
|
#define LOCKSIZE 1024
|
|
|
|
#define SKIPTHISFILE (char *)-1
|
2020-07-28 16:51:15 +00:00
|
|
|
|
2020-02-10 08:52:41 +00:00
|
|
|
const char *locking_prefix = ".";
|
|
|
|
const char *locking_suffix = ".swp";
|
|
|
|
|
2020-02-09 19:10:28 +00:00
|
|
|
/* Write a lock file, under the given lockfilename. This always annihilates an
|
|
|
|
* existing version of that file. Return TRUE on success; FALSE otherwise. */
|
2020-02-09 13:30:57 +00:00
|
|
|
bool write_lockfile(const char *lockfilename, const char *filename, bool modified)
|
2013-01-01 03:24:39 +00:00
|
|
|
{
|
2017-02-21 22:04:44 +00:00
|
|
|
#ifdef HAVE_PWD_H
|
2020-02-09 13:41:35 +00:00
|
|
|
pid_t mypid = getpid();
|
|
|
|
uid_t myuid = geteuid();
|
|
|
|
struct passwd *mypwuid = getpwuid(myuid);
|
|
|
|
char myhostname[32];
|
2020-05-20 14:17:30 +00:00
|
|
|
int fd;
|
2020-05-21 08:47:04 +00:00
|
|
|
FILE *filestream = NULL;
|
2020-02-02 11:04:00 +00:00
|
|
|
char *lockdata;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t wroteamt;
|
|
|
|
|
2020-02-09 13:41:35 +00:00
|
|
|
if (mypwuid == NULL) {
|
2020-07-22 09:38:29 +00:00
|
|
|
/* TRANSLATORS: Keep the next seven messages at most 76 characters. */
|
2019-06-19 17:34:19 +00:00
|
|
|
statusline(MILD, _("Couldn't determine my identity for lock file"));
|
2020-02-09 13:30:57 +00:00
|
|
|
return FALSE;
|
2015-11-06 20:14:37 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-02-10 08:40:12 +00:00
|
|
|
if (gethostname(myhostname, 31) < 0 && errno != ENAMETOOLONG) {
|
|
|
|
statusline(MILD, _("Couldn't determine hostname: %s"), strerror(errno));
|
|
|
|
return FALSE;
|
|
|
|
} else
|
|
|
|
myhostname[31] = '\0';
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-05-20 14:53:19 +00:00
|
|
|
/* First make sure to remove an existing lock file. */
|
|
|
|
if (!delete_lockfile(lockfilename))
|
|
|
|
return FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-05-20 14:53:19 +00:00
|
|
|
/* Create the lock file -- do not accept an existing one. */
|
2020-05-20 14:17:30 +00:00
|
|
|
fd = open(lockfilename, O_WRONLY|O_CREAT|O_EXCL, RW_FOR_ALL);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-05-20 14:17:30 +00:00
|
|
|
if (fd > 0)
|
|
|
|
filestream = fdopen(fd, "wb");
|
|
|
|
|
2020-05-21 08:47:04 +00:00
|
|
|
if (filestream == NULL) {
|
2020-01-13 13:14:25 +00:00
|
|
|
statusline(MILD, _("Error writing lock file %s: %s"),
|
2020-01-30 18:50:13 +00:00
|
|
|
lockfilename, strerror(errno));
|
2020-05-20 14:17:30 +00:00
|
|
|
if (fd > 0)
|
|
|
|
close(fd);
|
2020-02-09 13:30:57 +00:00
|
|
|
return FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 11:04:00 +00:00
|
|
|
lockdata = charalloc(LOCKSIZE);
|
|
|
|
memset(lockdata, 0, LOCKSIZE);
|
|
|
|
|
2020-02-10 08:52:41 +00:00
|
|
|
/* This is the lock data we will store (other bytes remain 0x00):
|
2017-12-29 18:27:33 +00:00
|
|
|
*
|
2020-01-31 10:39:11 +00:00
|
|
|
* bytes 0-1 - 0x62 0x30
|
2020-01-30 15:35:42 +00:00
|
|
|
* bytes 2-11 - name of program that created the lock
|
2020-01-30 14:55:42 +00:00
|
|
|
* bytes 24-27 - PID (little endian) of creator process
|
2020-02-10 08:52:41 +00:00
|
|
|
* bytes 28-43 - username of the user who created the lock
|
|
|
|
* bytes 68-99 - hostname of machine from where the lock was created
|
|
|
|
* bytes 108-876 - filename that the lock is for
|
2020-01-30 14:55:42 +00:00
|
|
|
* byte 1007 - 0x55 if file is modified
|
2017-12-29 18:27:33 +00:00
|
|
|
*
|
2020-01-30 15:35:42 +00:00
|
|
|
* Nano does not write the page size (bytes 12-15), nor the modification
|
|
|
|
* time (bytes 16-19), nor the inode of the relevant file (bytes 20-23).
|
|
|
|
* Nano also does not use all available space for user name (40 bytes),
|
|
|
|
* host name (40 bytes), and file name (890 bytes). Nor does nano write
|
|
|
|
* some byte-order-checking numbers (bytes 1008-1022). */
|
2017-12-29 18:27:33 +00:00
|
|
|
lockdata[0] = 0x62;
|
|
|
|
lockdata[1] = 0x30;
|
2020-01-30 15:35:42 +00:00
|
|
|
snprintf(&lockdata[2], 10, "nano %s", VERSION);
|
2017-12-29 18:27:33 +00:00
|
|
|
lockdata[24] = mypid % 256;
|
|
|
|
lockdata[25] = (mypid / 256) % 256;
|
|
|
|
lockdata[26] = (mypid / (256 * 256)) % 256;
|
|
|
|
lockdata[27] = mypid / (256 * 256 * 256);
|
|
|
|
strncpy(&lockdata[28], mypwuid->pw_name, 16);
|
2019-12-06 11:34:57 +00:00
|
|
|
strncpy(&lockdata[68], myhostname, 32);
|
2020-02-02 10:13:54 +00:00
|
|
|
strncpy(&lockdata[108], filename, 768);
|
|
|
|
lockdata[1007] = (modified) ? 0x55 : 0x00;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-01-31 10:06:11 +00:00
|
|
|
wroteamt = fwrite(lockdata, sizeof(char), LOCKSIZE, filestream);
|
2020-01-30 18:50:13 +00:00
|
|
|
|
2020-02-02 11:04:00 +00:00
|
|
|
free(lockdata);
|
|
|
|
|
2020-01-31 18:31:55 +00:00
|
|
|
if (fclose(filestream) == EOF || wroteamt < LOCKSIZE) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(MILD, _("Error writing lock file %s: %s"),
|
2020-01-30 18:50:13 +00:00
|
|
|
lockfilename, strerror(errno));
|
2020-02-09 13:30:57 +00:00
|
|
|
return FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-02-21 22:04:44 +00:00
|
|
|
#endif
|
2020-02-09 13:30:57 +00:00
|
|
|
return TRUE;
|
2013-01-01 03:24:39 +00:00
|
|
|
}
|
|
|
|
|
2020-02-09 19:10:28 +00:00
|
|
|
/* First check if a lock file already exists. If so, and ask_the_user is TRUE,
|
|
|
|
* then ask whether to open the corresponding file anyway. Return SKIPTHISFILE
|
|
|
|
* when the user answers "No", return the name of the lock file on success, and
|
|
|
|
* return NULL on failure. */
|
2020-02-09 15:45:23 +00:00
|
|
|
char *do_lockfile(const char *filename, bool ask_the_user)
|
2013-01-01 03:24:39 +00:00
|
|
|
{
|
2019-10-13 10:24:27 +00:00
|
|
|
char *namecopy = copy_of(filename);
|
|
|
|
char *secondcopy = copy_of(filename);
|
2020-01-14 09:35:54 +00:00
|
|
|
size_t locknamesize = strlen(filename) + strlen(locking_prefix) +
|
|
|
|
strlen(locking_suffix) + 3;
|
2017-12-29 18:27:33 +00:00
|
|
|
char *lockfilename = charalloc(locknamesize);
|
|
|
|
struct stat fileinfo;
|
2016-01-29 16:58:02 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
snprintf(lockfilename, locknamesize, "%s/%s%s%s", dirname(namecopy),
|
|
|
|
locking_prefix, basename(secondcopy), locking_suffix);
|
2020-01-14 10:01:32 +00:00
|
|
|
free(secondcopy);
|
|
|
|
free(namecopy);
|
2015-06-27 15:10:58 +00:00
|
|
|
|
2020-01-13 20:42:44 +00:00
|
|
|
if (!ask_the_user && stat(lockfilename, &fileinfo) != -1)
|
2020-05-28 14:59:50 +00:00
|
|
|
warn_and_briefly_pause(_("Someone else is also editing this file"));
|
2020-01-13 20:42:44 +00:00
|
|
|
else if (stat(lockfilename, &fileinfo) != -1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
char *lockbuf, *question, *pidstring, *postedname, *promptstr;
|
2020-01-14 09:35:54 +00:00
|
|
|
static char lockprog[11], lockuser[17];
|
|
|
|
int lockfd, lockpid, room, choice;
|
2020-02-02 10:36:01 +00:00
|
|
|
ssize_t readamt;
|
2016-08-30 09:45:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
|
2020-01-30 18:04:28 +00:00
|
|
|
statusline(ALERT, _("Error opening lock file %s: %s"),
|
2020-01-30 18:50:13 +00:00
|
|
|
lockfilename, strerror(errno));
|
2020-02-09 15:35:18 +00:00
|
|
|
free(lockfilename);
|
2020-02-09 15:45:23 +00:00
|
|
|
return NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2016-01-29 16:58:02 +00:00
|
|
|
|
2020-01-31 10:06:11 +00:00
|
|
|
lockbuf = charalloc(LOCKSIZE);
|
2020-01-30 18:50:13 +00:00
|
|
|
|
2020-02-02 10:36:01 +00:00
|
|
|
readamt = read(lockfd, lockbuf, LOCKSIZE);
|
2016-04-06 08:41:42 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(lockfd);
|
2016-05-18 19:04:39 +00:00
|
|
|
|
2020-02-06 15:00:48 +00:00
|
|
|
/* If not enough data has been read to show the needed things,
|
|
|
|
* or the two magic bytes are not there, skip the lock file. */
|
|
|
|
if (readamt < 68 || lockbuf[0] != 0x62 || lockbuf[1] != 0x30) {
|
2020-01-30 16:29:30 +00:00
|
|
|
statusline(ALERT, _("Bad lock file is ignored: %s"), lockfilename);
|
2020-02-09 15:35:18 +00:00
|
|
|
free(lockfilename);
|
2020-01-30 16:29:30 +00:00
|
|
|
free(lockbuf);
|
2020-02-09 15:45:23 +00:00
|
|
|
return NULL;
|
2020-01-30 16:29:30 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
strncpy(lockprog, &lockbuf[2], 10);
|
2020-01-30 19:00:17 +00:00
|
|
|
lockprog[10] = '\0';
|
2017-12-29 18:27:33 +00:00
|
|
|
lockpid = (((unsigned char)lockbuf[27] * 256 + (unsigned char)lockbuf[26]) * 256 +
|
|
|
|
(unsigned char)lockbuf[25]) * 256 + (unsigned char)lockbuf[24];
|
|
|
|
strncpy(lockuser, &lockbuf[28], 16);
|
2020-01-30 19:00:17 +00:00
|
|
|
lockuser[16] = '\0';
|
2017-12-29 18:27:33 +00:00
|
|
|
free(lockbuf);
|
|
|
|
|
|
|
|
pidstring = charalloc(11);
|
|
|
|
sprintf (pidstring, "%u", (unsigned int)lockpid);
|
|
|
|
|
2018-01-25 10:00:19 +00:00
|
|
|
/* Display newlines in filenames as ^J. */
|
|
|
|
as_an_at = FALSE;
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* TRANSLATORS: The second %s is the name of the user, the third that of the editor. */
|
2020-01-30 18:30:38 +00:00
|
|
|
question = _("File %s is being edited by %s (with %s, PID %s); open anyway?");
|
2019-04-24 06:49:18 +00:00
|
|
|
room = COLS - breadth(question) + 7 - breadth(lockuser) -
|
|
|
|
breadth(lockprog) - breadth(pidstring);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (room < 4)
|
2019-10-13 10:24:27 +00:00
|
|
|
postedname = copy_of("_");
|
2019-04-24 06:49:18 +00:00
|
|
|
else if (room < breadth(filename)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
char *fragment = display_string(filename,
|
2019-04-24 06:49:18 +00:00
|
|
|
breadth(filename) - room + 3, room, FALSE, FALSE);
|
2017-12-29 18:27:33 +00:00
|
|
|
postedname = charalloc(strlen(fragment) + 4);
|
|
|
|
strcpy(postedname, "...");
|
|
|
|
strcat(postedname, fragment);
|
|
|
|
free(fragment);
|
|
|
|
} else
|
2019-02-27 20:37:53 +00:00
|
|
|
postedname = display_string(filename, 0, room, FALSE, FALSE);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Allow extra space for username (14), program name (8), PID (8),
|
|
|
|
* and terminating \0 (1), minus the %s (2) for the file name. */
|
|
|
|
promptstr = charalloc(strlen(question) + 29 + strlen(postedname));
|
|
|
|
sprintf(promptstr, question, postedname, lockuser, lockprog, pidstring);
|
|
|
|
free(postedname);
|
|
|
|
free(pidstring);
|
|
|
|
|
2019-02-10 15:11:57 +00:00
|
|
|
choice = do_yesno_prompt(FALSE, promptstr);
|
2017-12-29 18:27:33 +00:00
|
|
|
free(promptstr);
|
|
|
|
|
2020-02-02 11:54:34 +00:00
|
|
|
/* When the user cancelled while we're still starting up, quit. */
|
|
|
|
if (choice < 0 && !we_are_running)
|
|
|
|
finish();
|
|
|
|
|
2019-02-10 15:11:57 +00:00
|
|
|
if (choice < 1) {
|
2020-02-09 15:35:18 +00:00
|
|
|
free(lockfilename);
|
2017-12-29 18:27:33 +00:00
|
|
|
wipe_statusbar();
|
2020-02-09 15:45:23 +00:00
|
|
|
return SKIPTHISFILE;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2015-06-27 15:10:58 +00:00
|
|
|
}
|
2016-01-29 16:58:02 +00:00
|
|
|
|
2020-02-09 15:45:23 +00:00
|
|
|
if (write_lockfile(lockfilename, filename, FALSE))
|
|
|
|
return lockfilename;
|
2016-01-29 16:58:02 +00:00
|
|
|
|
2020-02-09 15:35:18 +00:00
|
|
|
free(lockfilename);
|
2020-02-09 15:45:23 +00:00
|
|
|
return NULL;
|
2013-01-01 03:24:39 +00:00
|
|
|
}
|
2016-02-09 20:53:11 +00:00
|
|
|
|
|
|
|
/* Perform a stat call on the given filename, allocating a stat struct
|
|
|
|
* if necessary. On success, *pstat points to the stat's result. On
|
|
|
|
* failure, *pstat is freed and made NULL. */
|
|
|
|
void stat_with_alloc(const char *filename, struct stat **pstat)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*pstat == NULL)
|
|
|
|
*pstat = (struct stat *)nmalloc(sizeof(struct stat));
|
2016-02-09 20:53:11 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (stat(filename, *pstat) != 0) {
|
|
|
|
free(*pstat);
|
|
|
|
*pstat = NULL;
|
|
|
|
}
|
2016-02-09 20:53:11 +00:00
|
|
|
}
|
2014-03-17 14:15:57 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2013-01-01 03:24:39 +00:00
|
|
|
|
2020-02-10 08:55:21 +00:00
|
|
|
/* Verify that the containing directory of the given filename exists. */
|
|
|
|
bool has_valid_path(const char *filename)
|
|
|
|
{
|
|
|
|
char *namecopy = copy_of(filename);
|
|
|
|
char *parentdir = dirname(namecopy);
|
|
|
|
struct stat parentinfo;
|
|
|
|
bool validity = FALSE;
|
|
|
|
|
|
|
|
if (stat(parentdir, &parentinfo) == -1) {
|
|
|
|
if (errno == ENOENT)
|
2020-07-22 09:38:29 +00:00
|
|
|
/* TRANSLATORS: Keep the next ten messages at most 76 characters. */
|
2020-02-10 08:55:21 +00:00
|
|
|
statusline(ALERT, _("Directory '%s' does not exist"), parentdir);
|
|
|
|
else
|
|
|
|
statusline(ALERT, _("Path '%s': %s"), parentdir, strerror(errno));
|
|
|
|
} else if (!S_ISDIR(parentinfo.st_mode))
|
|
|
|
statusline(ALERT, _("Path '%s' is not a directory"), parentdir);
|
|
|
|
else if (access(parentdir, X_OK) == -1)
|
|
|
|
statusline(ALERT, _("Path '%s' is not accessible"), parentdir);
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
else if (ISSET(LOCKING) && !ISSET(VIEW_MODE) && access(parentdir, W_OK) < 0)
|
|
|
|
statusline(MILD, _("Directory '%s' is not writable"), parentdir);
|
|
|
|
#endif
|
|
|
|
else
|
|
|
|
validity = TRUE;
|
|
|
|
|
|
|
|
free(namecopy);
|
|
|
|
|
|
|
|
return validity;
|
|
|
|
}
|
|
|
|
|
2018-03-21 10:36:00 +00:00
|
|
|
/* This does one of three things. If the filename is "", it just creates
|
|
|
|
* a new empty buffer. When the filename is not empty, it reads that file
|
|
|
|
* into a new buffer when requested, otherwise into the existing buffer. */
|
2020-02-10 15:11:07 +00:00
|
|
|
bool open_buffer(const char *filename, bool new_one)
|
2005-07-08 02:47:05 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *realname;
|
|
|
|
/* The filename after tilde expansion. */
|
2020-02-09 16:35:00 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
char *thelocksname = NULL;
|
|
|
|
#endif
|
2019-05-27 07:38:08 +00:00
|
|
|
struct stat fileinfo;
|
2020-02-09 18:50:33 +00:00
|
|
|
int descriptor = 0;
|
|
|
|
/* Code 0 means new file, -1 means failure, and else it's the fd. */
|
2020-02-09 19:10:28 +00:00
|
|
|
FILE *f;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Display newlines in filenames as ^J. */
|
|
|
|
as_an_at = FALSE;
|
2016-12-26 11:35:50 +00:00
|
|
|
|
2017-10-29 20:08:07 +00:00
|
|
|
#ifdef ENABLE_OPERATINGDIR
|
2019-05-29 09:42:23 +00:00
|
|
|
if (outside_of_confinement(filename, FALSE)) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Can't read file from outside of %s"), operating_dir);
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
2005-07-08 20:09:16 +00:00
|
|
|
#endif
|
2005-07-08 02:47:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
realname = real_dir_from_tilde(filename);
|
2016-04-23 16:13:43 +00:00
|
|
|
|
2019-05-27 07:38:08 +00:00
|
|
|
/* Don't try to open directories, character files, or block files. */
|
|
|
|
if (*filename != '\0' && stat(realname, &fileinfo) == 0) {
|
|
|
|
if (S_ISDIR(fileinfo.st_mode)) {
|
2019-05-12 08:17:36 +00:00
|
|
|
statusline(ALERT, _("\"%s\" is a directory"), realname);
|
2017-12-29 18:27:33 +00:00
|
|
|
free(realname);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2019-05-27 07:38:08 +00:00
|
|
|
if (S_ISCHR(fileinfo.st_mode) || S_ISBLK(fileinfo.st_mode)) {
|
|
|
|
statusline(ALERT, _("\"%s\" is a device file"), realname);
|
|
|
|
free(realname);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2019-05-27 07:55:24 +00:00
|
|
|
#ifdef NANO_TINY
|
|
|
|
if (S_ISFIFO(fileinfo.st_mode)) {
|
|
|
|
statusline(ALERT, _("\"%s\" is a FIFO"), realname);
|
|
|
|
free(realname);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2020-07-01 16:47:14 +00:00
|
|
|
#else
|
|
|
|
if (new_one && !(fileinfo.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) &&
|
|
|
|
geteuid() == ROOT_UID)
|
|
|
|
statusline(ALERT, _("%s is meant to be read-only"), realname);
|
2019-05-27 07:55:24 +00:00
|
|
|
#endif
|
2015-07-17 20:40:44 +00:00
|
|
|
}
|
|
|
|
|
2020-02-09 19:10:28 +00:00
|
|
|
/* When loading into a new buffer, first check the file's path is valid,
|
|
|
|
* and then (if requested and possible) create a lock file for it. */
|
2020-02-10 15:11:07 +00:00
|
|
|
if (new_one && has_valid_path(realname)) {
|
2015-01-14 02:36:30 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-02-09 19:17:05 +00:00
|
|
|
if (ISSET(LOCKING) && !ISSET(VIEW_MODE) && filename[0] != '\0') {
|
|
|
|
thelocksname = do_lockfile(realname, TRUE);
|
|
|
|
|
|
|
|
/* When not overriding an existing lock, don't open a buffer. */
|
|
|
|
if (thelocksname == SKIPTHISFILE) {
|
|
|
|
free(realname);
|
|
|
|
return FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-02-09 19:17:05 +00:00
|
|
|
}
|
2020-02-09 19:10:28 +00:00
|
|
|
#endif
|
2016-05-17 11:37:53 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-02-10 15:11:07 +00:00
|
|
|
if (new_one)
|
2020-02-09 16:35:00 +00:00
|
|
|
make_new_buffer();
|
|
|
|
|
2020-02-09 19:10:28 +00:00
|
|
|
/* If we have a filename and are not in NOREAD mode, open the file. */
|
2020-02-09 18:50:33 +00:00
|
|
|
if (filename[0] != '\0' && !ISSET(NOREAD_MODE))
|
2020-02-10 15:11:07 +00:00
|
|
|
descriptor = open_file(realname, new_one, &f);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-02-09 19:10:28 +00:00
|
|
|
/* If we've successfully opened an existing file, read it in. */
|
2020-02-09 18:50:33 +00:00
|
|
|
if (descriptor > 0) {
|
2019-05-26 11:32:28 +00:00
|
|
|
install_handler_for_Ctrl_C();
|
2019-05-24 17:58:35 +00:00
|
|
|
|
2020-02-10 15:11:07 +00:00
|
|
|
read_file(f, descriptor, realname, !new_one);
|
2019-05-24 17:58:35 +00:00
|
|
|
|
2019-05-26 11:32:28 +00:00
|
|
|
restore_handler_for_Ctrl_C();
|
2019-05-24 17:58:35 +00:00
|
|
|
|
2019-05-26 11:32:28 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-05-28 12:31:15 +00:00
|
|
|
if (openfile->statinfo == NULL)
|
|
|
|
stat_with_alloc(realname, &openfile->statinfo);
|
2001-10-14 19:05:10 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-08 20:09:16 +00:00
|
|
|
|
2020-02-09 19:10:28 +00:00
|
|
|
/* When we've loaded a file into a new buffer, set the filename
|
|
|
|
* and put the cursor at the start of the buffer. */
|
2020-02-10 15:11:07 +00:00
|
|
|
if (descriptor >= 0 && new_one) {
|
2018-03-21 09:41:47 +00:00
|
|
|
openfile->filename = mallocstrcpy(openfile->filename, realname);
|
2020-02-09 16:35:00 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
openfile->lock_filename = thelocksname;
|
|
|
|
#endif
|
2019-03-21 16:23:49 +00:00
|
|
|
openfile->current = openfile->filetop;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = 0;
|
|
|
|
openfile->placewewant = 0;
|
|
|
|
}
|
2005-07-13 20:18:46 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2020-04-28 14:32:04 +00:00
|
|
|
/* If a new buffer was opened, check whether a syntax can be applied. */
|
2020-02-10 15:11:07 +00:00
|
|
|
if (new_one)
|
2020-04-28 14:32:04 +00:00
|
|
|
find_and_prime_applicable_syntax();
|
2005-07-13 20:18:46 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
free(realname);
|
|
|
|
return TRUE;
|
2005-07-09 04:42:47 +00:00
|
|
|
}
|
2003-09-23 04:25:05 +00:00
|
|
|
|
2020-02-02 11:25:09 +00:00
|
|
|
/* Mark the current file as modified if it isn't already, and
|
|
|
|
* then update the title bar to display the file's new status. */
|
|
|
|
void set_modified(void)
|
|
|
|
{
|
|
|
|
if (openfile->modified)
|
|
|
|
return;
|
|
|
|
|
|
|
|
openfile->modified = TRUE;
|
|
|
|
titlebar(NULL);
|
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
if (openfile->lock_filename != NULL)
|
|
|
|
write_lockfile(openfile->lock_filename, openfile->filename, TRUE);
|
|
|
|
#endif
|
|
|
|
}
|
2005-11-08 19:15:58 +00:00
|
|
|
|
2020-01-16 15:16:31 +00:00
|
|
|
/* Update the title bar and the multiline cache to match the current buffer. */
|
2017-05-01 14:48:13 +00:00
|
|
|
void prepare_for_display(void)
|
2005-07-09 04:42:47 +00:00
|
|
|
{
|
2020-01-16 15:16:31 +00:00
|
|
|
/* Update the title bar, since the filename may have changed. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!inhelp)
|
|
|
|
titlebar(NULL);
|
2005-07-14 23:06:22 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2020-05-01 11:52:25 +00:00
|
|
|
/* Precalculate the data for any multiline coloring regexes. */
|
|
|
|
precalc_multicolorinfo();
|
2017-12-29 18:27:33 +00:00
|
|
|
have_palette = FALSE;
|
2017-05-01 14:48:13 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2000-06-19 04:22:15 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-07-12 11:09:17 +00:00
|
|
|
/* Show name of current buffer and its number of lines on the status bar. */
|
|
|
|
void mention_name_and_linecount(void)
|
|
|
|
{
|
|
|
|
size_t count = openfile->filebot->lineno -
|
|
|
|
(openfile->filebot->data[0] == '\0' ? 1 : 0);
|
2018-07-13 09:15:48 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if (openfile->fmt != NIX_FILE)
|
2020-03-11 13:18:06 +00:00
|
|
|
/* TRANSLATORS: First %s is file name, second %s is file format. */
|
2018-07-13 09:15:48 +00:00
|
|
|
statusline(HUSH, P_("%s -- %zu line (%s)", "%s -- %zu lines (%s)", count),
|
|
|
|
openfile->filename[0] == '\0' ?
|
|
|
|
_("New Buffer") : tail(openfile->filename), count,
|
|
|
|
openfile->fmt == DOS_FILE ? _("DOS") : _("Mac"));
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
statusline(HUSH, P_("%s -- %zu line", "%s -- %zu lines", count),
|
2018-07-12 11:09:17 +00:00
|
|
|
openfile->filename[0] == '\0' ?
|
|
|
|
_("New Buffer") : tail(openfile->filename), count);
|
|
|
|
}
|
|
|
|
|
2019-05-31 07:33:28 +00:00
|
|
|
/* Update title bar and such after switching to another buffer.*/
|
|
|
|
void redecorate_after_switch(void)
|
2000-06-19 04:22:15 +00:00
|
|
|
{
|
2019-05-31 07:33:28 +00:00
|
|
|
/* If only one file buffer is open, there is nothing to update. */
|
2019-05-30 15:18:30 +00:00
|
|
|
if (openfile == openfile->next) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("No more open file buffers"));
|
|
|
|
return;
|
|
|
|
}
|
2005-07-08 02:47:05 +00:00
|
|
|
|
2017-01-21 16:54:47 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-06-07 09:51:14 +00:00
|
|
|
/* While in a different buffer, the effective width of the screen may
|
|
|
|
* have changed, so make sure that the softwrapped chunks per line and
|
|
|
|
* the starting column for the first row get corresponding values. */
|
|
|
|
compute_the_extra_rows_per_line_from(openfile->filetop);
|
2020-05-13 10:01:55 +00:00
|
|
|
ensure_firstcolumn_is_aligned();
|
2017-01-21 16:54:47 +00:00
|
|
|
#endif
|
|
|
|
|
2020-01-16 15:16:31 +00:00
|
|
|
/* Update title bar and multiline info to match the current buffer. */
|
2017-12-29 18:27:33 +00:00
|
|
|
prepare_for_display();
|
2005-07-09 04:42:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Ensure that the main loop will redraw the help lines. */
|
|
|
|
currmenu = MMOST;
|
2017-07-13 08:55:02 +00:00
|
|
|
|
2019-01-21 11:18:37 +00:00
|
|
|
/* Prevent a possible Shift selection from getting cancelled. */
|
|
|
|
shift_held = TRUE;
|
|
|
|
|
2018-07-12 11:09:17 +00:00
|
|
|
/* Indicate on the status bar where we switched to. */
|
|
|
|
mention_name_and_linecount();
|
2000-06-19 04:22:15 +00:00
|
|
|
}
|
|
|
|
|
2017-08-31 20:14:06 +00:00
|
|
|
/* Switch to the previous entry in the list of open files. */
|
2017-08-31 20:00:53 +00:00
|
|
|
void switch_to_prev_buffer(void)
|
2002-02-22 04:30:50 +00:00
|
|
|
{
|
2019-05-31 07:33:28 +00:00
|
|
|
openfile = openfile->prev;
|
|
|
|
redecorate_after_switch();
|
2005-07-08 02:47:05 +00:00
|
|
|
}
|
2005-05-29 02:22:55 +00:00
|
|
|
|
2017-08-31 20:14:06 +00:00
|
|
|
/* Switch to the next entry in the list of open files. */
|
2017-08-31 20:00:53 +00:00
|
|
|
void switch_to_next_buffer(void)
|
2005-07-08 02:47:05 +00:00
|
|
|
{
|
2019-05-31 07:33:28 +00:00
|
|
|
openfile = openfile->next;
|
|
|
|
redecorate_after_switch();
|
2005-07-08 02:47:05 +00:00
|
|
|
}
|
2005-05-29 02:22:55 +00:00
|
|
|
|
2019-06-01 09:10:48 +00:00
|
|
|
/* Remove the current buffer from the circular list of buffers. */
|
|
|
|
void close_buffer(void)
|
2019-06-01 08:52:38 +00:00
|
|
|
{
|
2019-06-01 09:10:48 +00:00
|
|
|
openfilestruct *orphan = openfile;
|
|
|
|
|
|
|
|
if (orphan == startfile)
|
2019-06-01 08:52:38 +00:00
|
|
|
startfile = startfile->next;
|
|
|
|
|
2019-06-01 09:10:48 +00:00
|
|
|
orphan->prev->next = orphan->next;
|
|
|
|
orphan->next->prev = orphan->prev;
|
2019-06-01 08:52:38 +00:00
|
|
|
|
2019-06-01 09:10:48 +00:00
|
|
|
free(orphan->filename);
|
|
|
|
free_lines(orphan->filetop);
|
2019-06-01 08:52:38 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-05-28 12:31:15 +00:00
|
|
|
free(orphan->statinfo);
|
2019-06-01 09:10:48 +00:00
|
|
|
free(orphan->lock_filename);
|
2019-06-01 08:52:38 +00:00
|
|
|
/* Free the undo stack. */
|
2020-03-08 11:54:47 +00:00
|
|
|
discard_until(NULL);
|
2019-06-01 08:52:38 +00:00
|
|
|
#endif
|
|
|
|
|
2019-06-01 09:10:48 +00:00
|
|
|
openfile = orphan->prev;
|
|
|
|
free(orphan);
|
2002-02-22 04:30:50 +00:00
|
|
|
|
2019-05-30 14:37:16 +00:00
|
|
|
/* When just one buffer remains open, show "Exit" in the help lines. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile == openfile->next)
|
|
|
|
exitfunc->desc = exit_tag;
|
2002-02-22 04:30:50 +00:00
|
|
|
}
|
2017-05-01 18:20:34 +00:00
|
|
|
#endif /* ENABLE_MULTIBUFFER */
|
2002-02-22 04:30:50 +00:00
|
|
|
|
2020-04-15 15:50:59 +00:00
|
|
|
/* Encode any NUL bytes in the given line of text (of the given length),
|
2017-02-09 18:26:27 +00:00
|
|
|
* and return a dynamically allocated copy of the resultant string. */
|
2020-04-15 15:50:59 +00:00
|
|
|
char *encode_data(char *text, size_t length)
|
2004-09-05 21:40:31 +00:00
|
|
|
{
|
2020-04-15 15:50:59 +00:00
|
|
|
recode_NUL_to_LF(text, length);
|
|
|
|
text[length] = '\0';
|
2004-08-27 21:02:38 +00:00
|
|
|
|
2020-04-15 15:50:59 +00:00
|
|
|
return copy_of(text);
|
2005-07-08 02:47:05 +00:00
|
|
|
}
|
2004-11-05 16:24:35 +00:00
|
|
|
|
2020-05-11 14:44:16 +00:00
|
|
|
/* The number of bytes by which we expand the line buffer while reading. */
|
|
|
|
#define LUMPSIZE 120
|
|
|
|
|
2018-03-22 09:41:39 +00:00
|
|
|
/* Read the given open file f into the current buffer. filename should be
|
|
|
|
* set to the name of the file. undoable means that undo records should be
|
|
|
|
* created and that the file does not need to be checked for writability. */
|
|
|
|
void read_file(FILE *f, int fd, const char *filename, bool undoable)
|
2005-07-08 02:47:05 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
ssize_t was_lineno = openfile->current->lineno;
|
|
|
|
/* The line number where we start the insertion. */
|
|
|
|
size_t was_leftedge = 0;
|
|
|
|
/* The leftedge where we start the insertion. */
|
|
|
|
size_t num_lines = 0;
|
|
|
|
/* The number of lines in the file. */
|
|
|
|
size_t len = 0;
|
|
|
|
/* The length of the current line of the file. */
|
2020-05-11 14:59:39 +00:00
|
|
|
size_t bufsize = LUMPSIZE;
|
|
|
|
/* The size of the line buffer; increased as needed. */
|
|
|
|
char *buf = charalloc(bufsize);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The buffer in which we assemble each line of the file. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *topline;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The top of the new buffer where we store the read file. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *bottomline;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The bottom of the new buffer. */
|
2020-05-11 14:59:39 +00:00
|
|
|
int onevalue;
|
|
|
|
/* The current value we read from the file, either a byte or EOF. */
|
2019-05-28 09:06:42 +00:00
|
|
|
int errornumber;
|
|
|
|
/* The error code, in case an error occurred during reading. */
|
2017-12-29 18:27:33 +00:00
|
|
|
bool writable = TRUE;
|
|
|
|
/* Whether the file is writable (in case we care). */
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
int format = 0;
|
2020-05-12 15:17:28 +00:00
|
|
|
/* 0 = Unix, 1 = DOS, 2 = Mac. */
|
2004-10-01 18:34:30 +00:00
|
|
|
#endif
|
2002-09-03 22:58:40 +00:00
|
|
|
|
2008-08-03 04:48:05 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (undoable)
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(INSERT, NULL);
|
2017-02-08 04:09:16 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(SOFTWRAP))
|
|
|
|
was_leftedge = leftedge_for(xplustabs(), openfile->current);
|
2008-08-03 04:48:05 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Create an empty buffer. */
|
|
|
|
topline = make_new_node(NULL);
|
|
|
|
bottomline = topline;
|
2017-02-09 18:26:27 +00:00
|
|
|
|
2019-03-31 23:17:45 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
block_sigwinch(TRUE);
|
|
|
|
#endif
|
|
|
|
|
2018-06-08 16:38:19 +00:00
|
|
|
/* Lock the file before starting to read it, to avoid the overhead
|
|
|
|
* of locking it for each single byte that we read from it. */
|
|
|
|
flockfile(f);
|
|
|
|
|
2019-05-24 17:58:35 +00:00
|
|
|
control_C_was_pressed = FALSE;
|
|
|
|
|
2020-05-11 14:59:39 +00:00
|
|
|
/* Read in the entire file, byte by byte, line by line. */
|
|
|
|
while ((onevalue = getc_unlocked(f)) != EOF) {
|
|
|
|
char input = (char)onevalue;
|
2019-05-24 17:58:35 +00:00
|
|
|
|
2020-08-02 08:23:45 +00:00
|
|
|
if (control_C_was_pressed)
|
2019-05-24 17:58:35 +00:00
|
|
|
break;
|
|
|
|
|
2020-05-12 18:53:08 +00:00
|
|
|
/* When the byte before the current one is a CR and we're doing
|
|
|
|
* format conversion, then strip this CR when it's before a LF
|
|
|
|
* OR when the file is in Mac format. Also, when still on the
|
|
|
|
* first line, set the format to either DOS (1) or Mac (2). */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (input == '\n') {
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-05-12 18:53:08 +00:00
|
|
|
if (len > 0 && buf[len - 1] == '\r' && !ISSET(NO_CONVERT)) {
|
|
|
|
if (num_lines == 0)
|
|
|
|
format = 1;
|
2020-05-13 08:31:39 +00:00
|
|
|
len--;
|
2020-05-12 18:53:08 +00:00
|
|
|
}
|
2020-05-13 09:11:24 +00:00
|
|
|
} else if ((num_lines == 0 || format == 2) &&
|
|
|
|
len > 0 && buf[len - 1] == '\r' && !ISSET(NO_CONVERT)) {
|
2020-05-12 15:17:28 +00:00
|
|
|
format = 2;
|
2020-05-13 08:31:39 +00:00
|
|
|
len--;
|
2005-03-31 17:00:43 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2020-05-11 13:57:12 +00:00
|
|
|
/* Store the byte. */
|
2017-12-29 18:27:33 +00:00
|
|
|
buf[len] = input;
|
|
|
|
|
|
|
|
/* Keep track of the total length of the line. It might have
|
2020-05-11 13:57:12 +00:00
|
|
|
* NUL bytes in it, so we can't just use strlen() later. */
|
2017-12-29 18:27:33 +00:00
|
|
|
len++;
|
|
|
|
|
2020-05-11 14:44:16 +00:00
|
|
|
/* When needed, increase the line-buffer size. Don't bother
|
|
|
|
* decreasing it -- it gets freed when reading is finished. */
|
2020-05-11 14:59:39 +00:00
|
|
|
if (len == bufsize) {
|
|
|
|
bufsize += LUMPSIZE;
|
|
|
|
buf = charealloc(buf, bufsize);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-05-11 13:57:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-02-09 18:26:27 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Store the data and make a new line. */
|
|
|
|
bottomline->data = encode_data(buf, len);
|
|
|
|
bottomline->next = make_new_node(bottomline);
|
|
|
|
bottomline = bottomline->next;
|
|
|
|
num_lines++;
|
2017-02-09 18:26:27 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Reset the length in preparation for the next line. */
|
|
|
|
len = 0;
|
2017-02-09 18:26:27 +00:00
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
2020-05-11 13:57:12 +00:00
|
|
|
/* If it was a Mac line, then store the byte after the \r
|
|
|
|
* as the first byte of the next line. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (input != '\n')
|
|
|
|
buf[len++] = input;
|
2017-02-09 18:26:27 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-02-09 18:26:27 +00:00
|
|
|
|
2019-05-28 09:06:42 +00:00
|
|
|
errornumber = errno;
|
|
|
|
|
2018-06-08 16:38:19 +00:00
|
|
|
/* We are done with the file, unlock it. */
|
|
|
|
funlockfile(f);
|
|
|
|
|
2019-03-31 23:17:45 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
block_sigwinch(FALSE);
|
|
|
|
#endif
|
|
|
|
|
2019-05-28 09:06:42 +00:00
|
|
|
/* When reading from stdin, restore the terminal and reenter curses mode. */
|
|
|
|
if (isendwin()) {
|
2019-05-28 10:31:16 +00:00
|
|
|
if (!isatty(STANDARD_INPUT))
|
2019-05-28 09:06:42 +00:00
|
|
|
reconnect_and_store_state();
|
|
|
|
terminal_init();
|
|
|
|
doupdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If there was a real error during the reading, let the user know. */
|
|
|
|
if (ferror(f) && errornumber != EINTR && errornumber != 0)
|
|
|
|
statusline(ALERT, strerror(errornumber));
|
2019-10-17 14:43:07 +00:00
|
|
|
|
2020-08-02 08:23:45 +00:00
|
|
|
if (control_C_was_pressed)
|
|
|
|
statusline(ALERT, _("Interrupted"));
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
fclose(f);
|
2019-05-28 09:06:42 +00:00
|
|
|
|
2020-05-11 17:40:17 +00:00
|
|
|
if (fd > 0 && !undoable && !ISSET(VIEW_MODE))
|
|
|
|
writable = (access(filename, W_OK) == 0);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If the file ended with newline, or it was entirely empty, make the
|
|
|
|
* last line blank. Otherwise, put the last read data in. */
|
|
|
|
if (len == 0)
|
2019-10-13 10:24:27 +00:00
|
|
|
bottomline->data = copy_of("");
|
2017-12-29 18:27:33 +00:00
|
|
|
else {
|
2020-05-12 09:09:23 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
bool mac_line_needs_newline = FALSE;
|
|
|
|
|
2020-05-12 15:17:28 +00:00
|
|
|
/* If the final character is a CR and file conversion isn't disabled,
|
|
|
|
* strip this CR and indicate that an extra blank line is needed. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (buf[len - 1] == '\r' && !ISSET(NO_CONVERT)) {
|
2020-05-12 15:17:28 +00:00
|
|
|
if (num_lines == 0)
|
|
|
|
format = 2;
|
2017-12-29 18:27:33 +00:00
|
|
|
buf[--len] = '\0';
|
|
|
|
mac_line_needs_newline = TRUE;
|
|
|
|
}
|
2017-02-09 18:26:27 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Store the data of the final line. */
|
|
|
|
bottomline->data = encode_data(buf, len);
|
|
|
|
num_lines++;
|
|
|
|
|
2020-05-12 09:09:23 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (mac_line_needs_newline) {
|
|
|
|
bottomline->next = make_new_node(bottomline);
|
|
|
|
bottomline = bottomline->next;
|
2019-10-13 10:24:27 +00:00
|
|
|
bottomline->data = copy_of("");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-05-12 09:09:23 +00:00
|
|
|
#endif
|
2005-11-05 17:20:39 +00:00
|
|
|
}
|
2001-07-11 02:08:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(buf);
|
2001-07-11 02:08:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Insert the just read buffer into the current one. */
|
|
|
|
ingraft_buffer(topline);
|
2005-11-05 17:20:39 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Set the desired x position at the end of what was inserted. */
|
|
|
|
openfile->placewewant = xplustabs();
|
2005-11-05 17:20:39 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!writable)
|
|
|
|
statusline(ALERT, _("File '%s' is unwritable"), filename);
|
2016-09-11 19:40:50 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-05-12 15:17:28 +00:00
|
|
|
else if (format == 2) {
|
|
|
|
/* TRANSLATORS: Keep the next three messages at most 78 characters. */
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->fmt = MAC_FILE;
|
|
|
|
statusline(HUSH, P_("Read %zu line (Converted from Mac format)",
|
|
|
|
"Read %zu lines (Converted from Mac format)",
|
|
|
|
num_lines), num_lines);
|
|
|
|
} else if (format == 1) {
|
|
|
|
openfile->fmt = DOS_FILE;
|
|
|
|
statusline(HUSH, P_("Read %zu line (Converted from DOS format)",
|
|
|
|
"Read %zu lines (Converted from DOS format)",
|
|
|
|
num_lines), num_lines);
|
|
|
|
}
|
2016-09-11 19:43:47 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
|
|
|
statusline(HUSH, P_("Read %zu line", "Read %zu lines",
|
|
|
|
num_lines), num_lines);
|
2015-08-04 18:49:57 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If we inserted less than a screenful, don't center the cursor. */
|
|
|
|
if (undoable && less_than_a_screenful(was_lineno, was_leftedge))
|
|
|
|
focusing = FALSE;
|
2016-04-12 08:24:57 +00:00
|
|
|
|
2015-08-09 16:31:01 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (undoable)
|
|
|
|
update_undo(INSERT);
|
2016-09-11 19:43:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(MAKE_IT_UNIX))
|
|
|
|
openfile->fmt = NIX_FILE;
|
2015-08-09 16:31:01 +00:00
|
|
|
#endif
|
2005-07-08 02:47:05 +00:00
|
|
|
}
|
2002-05-11 03:04:44 +00:00
|
|
|
|
2016-11-26 16:41:31 +00:00
|
|
|
/* Open the file with the given name. If the file does not exist, display
|
2020-02-10 15:11:07 +00:00
|
|
|
* "New File" if new_one is TRUE, and say "File not found" otherwise.
|
2020-02-10 16:13:12 +00:00
|
|
|
* Return 0 if we say "New File", -1 upon failure, and the obtained file
|
|
|
|
* descriptor otherwise. The opened filestream is returned in *f. */
|
2020-02-10 15:11:07 +00:00
|
|
|
int open_file(const char *filename, bool new_one, FILE **f)
|
2005-07-08 02:47:05 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *full_filename = get_full_path(filename);
|
2020-02-10 15:56:35 +00:00
|
|
|
struct stat fileinfo;
|
|
|
|
int fd;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-02-10 16:13:12 +00:00
|
|
|
/* If the absolute path is unusable (due to some component's permissions),
|
|
|
|
* try the given path instead (as it is probably relative). */
|
2020-02-10 15:56:35 +00:00
|
|
|
if (full_filename == NULL || stat(full_filename, &fileinfo) == -1)
|
2017-12-29 18:27:33 +00:00
|
|
|
full_filename = mallocstrcpy(full_filename, filename);
|
|
|
|
|
|
|
|
if (stat(full_filename, &fileinfo) == -1) {
|
2020-02-10 15:56:35 +00:00
|
|
|
free(full_filename);
|
|
|
|
|
2020-02-10 15:11:07 +00:00
|
|
|
if (new_one) {
|
2019-05-24 10:42:48 +00:00
|
|
|
statusbar(_("New File"));
|
2020-02-10 14:57:40 +00:00
|
|
|
return 0;
|
2020-02-10 15:56:35 +00:00
|
|
|
} else {
|
|
|
|
statusline(ALERT, _("File \"%s\" not found"), filename);
|
|
|
|
return -1;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-26 16:41:31 +00:00
|
|
|
|
2019-05-27 07:55:24 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-05-18 17:09:02 +00:00
|
|
|
if (S_ISFIFO(fileinfo.st_mode))
|
|
|
|
statusbar(_("Reading from FIFO..."));
|
|
|
|
|
2019-05-20 11:07:09 +00:00
|
|
|
block_sigwinch(TRUE);
|
2019-05-26 11:32:28 +00:00
|
|
|
install_handler_for_Ctrl_C();
|
2019-05-24 08:39:33 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Try opening the file. */
|
|
|
|
fd = open(full_filename, O_RDONLY);
|
2004-10-05 20:11:31 +00:00
|
|
|
|
2019-05-24 08:39:33 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-05-26 11:32:28 +00:00
|
|
|
restore_handler_for_Ctrl_C();
|
2019-05-20 11:07:09 +00:00
|
|
|
block_sigwinch(FALSE);
|
2019-05-24 08:39:33 +00:00
|
|
|
#endif
|
|
|
|
|
2019-05-26 12:17:35 +00:00
|
|
|
if (fd == -1) {
|
2019-05-26 17:45:58 +00:00
|
|
|
if (errno == EINTR || errno == 0)
|
2019-05-26 12:17:35 +00:00
|
|
|
statusline(ALERT, _("Interrupted"));
|
|
|
|
else
|
|
|
|
statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
|
|
|
|
} else {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The file is A-OK. Associate a stream with it. */
|
|
|
|
*f = fdopen(fd, "rb");
|
|
|
|
|
|
|
|
if (*f == NULL) {
|
|
|
|
statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
|
|
|
|
close(fd);
|
2019-05-24 15:22:04 +00:00
|
|
|
fd = -1;
|
2019-05-29 09:42:23 +00:00
|
|
|
} else
|
2019-05-18 17:09:02 +00:00
|
|
|
statusbar(_("Reading..."));
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-20 21:08:38 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(full_filename);
|
2007-04-18 18:22:13 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return fd;
|
2009-09-03 05:45:13 +00:00
|
|
|
}
|
|
|
|
|
2005-07-08 02:47:05 +00:00
|
|
|
/* This function will return the name of the first available extension
|
|
|
|
* of a filename (starting with [name][suffix], then [name][suffix].1,
|
|
|
|
* etc.). Memory is allocated for the return value. If no writable
|
|
|
|
* extension exists, we return "". */
|
|
|
|
char *get_next_filename(const char *name, const char *suffix)
|
2001-07-11 02:08:33 +00:00
|
|
|
{
|
2020-07-28 16:51:15 +00:00
|
|
|
size_t wholenamelen= strlen(name) + strlen(suffix);
|
2017-12-29 18:27:33 +00:00
|
|
|
unsigned long i = 0;
|
|
|
|
char *buf;
|
2005-11-09 15:13:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Reserve space for: the name plus the suffix plus a dot plus
|
|
|
|
* possibly five digits plus a null byte. */
|
|
|
|
buf = charalloc(wholenamelen + 7);
|
|
|
|
sprintf(buf, "%s%s", name, suffix);
|
2005-01-27 20:49:07 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
struct stat fs;
|
2002-07-19 01:08:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (stat(buf, &fs) == -1)
|
|
|
|
return buf;
|
2015-05-08 21:11:30 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Limit the number of backup files to a hundred thousand. */
|
|
|
|
if (++i == 100000)
|
|
|
|
break;
|
2005-01-27 20:49:07 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
sprintf(buf + wholenamelen, ".%lu", i);
|
|
|
|
}
|
2002-07-19 01:08:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* There is no possible save file: blank out the filename. */
|
|
|
|
*buf = '\0';
|
2005-01-27 20:49:07 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return buf;
|
2002-07-19 01:08:59 +00:00
|
|
|
}
|
|
|
|
|
2020-03-06 17:52:42 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-07-28 16:51:15 +00:00
|
|
|
static pid_t pid_of_command = -1;
|
|
|
|
/* The PID of a forked process -- needed when wanting to abort it. */
|
|
|
|
|
2020-03-06 17:52:42 +00:00
|
|
|
/* Send an unconditional kill signal to the running external command. */
|
|
|
|
RETSIGTYPE cancel_the_command(int signal)
|
|
|
|
{
|
|
|
|
kill(pid_of_command, SIGKILL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Send the text that starts at the given line to file descriptor fd. */
|
|
|
|
void send_data(const linestruct *line, int fd)
|
|
|
|
{
|
|
|
|
FILE *tube = fdopen(fd, "w");
|
|
|
|
|
|
|
|
if (tube == NULL)
|
|
|
|
exit(4);
|
|
|
|
|
|
|
|
/* Send each line, except a final empty line. */
|
|
|
|
while (line != NULL && (line->next != NULL || line->data[0] != '\0')) {
|
|
|
|
fprintf(tube, "%s%s", line->data, line->next == NULL ? "" : "\n");
|
|
|
|
line = line->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(tube);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Execute the given command in a shell. Return TRUE on success. */
|
|
|
|
bool execute_command(const char *command)
|
|
|
|
{
|
|
|
|
int from_fd[2], to_fd[2];
|
|
|
|
/* The pipes through which text will be written and read. */
|
|
|
|
const bool should_pipe = (command[0] == '|');
|
|
|
|
FILE *stream;
|
|
|
|
struct sigaction oldaction, newaction = {{0}};
|
|
|
|
/* Original and temporary handlers for SIGINT. */
|
|
|
|
|
|
|
|
/* Create a pipe to read the command's output from, and, if needed,
|
|
|
|
* a pipe to feed the command's input through. */
|
|
|
|
if (pipe(from_fd) == -1 || (should_pipe && pipe(to_fd) == -1)) {
|
|
|
|
statusline(ALERT, _("Could not create pipe"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fork a child process to run the command in. */
|
|
|
|
if ((pid_of_command = fork()) == 0) {
|
|
|
|
const char *theshell = getenv("SHELL");
|
|
|
|
|
|
|
|
if (theshell == NULL)
|
|
|
|
theshell = (char *)"/bin/sh";
|
|
|
|
|
|
|
|
/* Child: close the unused read end of the output pipe. */
|
|
|
|
close(from_fd[0]);
|
|
|
|
|
|
|
|
/* Connect the write end of the output pipe to the process' output streams. */
|
|
|
|
dup2(from_fd[1], fileno(stdout));
|
|
|
|
dup2(from_fd[1], fileno(stderr));
|
|
|
|
|
|
|
|
/* If the parent sends text, connect the read end of the
|
|
|
|
* feeding pipe to the child's input stream. */
|
|
|
|
if (should_pipe) {
|
|
|
|
dup2(to_fd[0], fileno(stdin));
|
|
|
|
close(to_fd[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Run the given command inside the preferred shell. */
|
|
|
|
execl(theshell, tail(theshell), "-c", should_pipe ? &command[1] : command, NULL);
|
|
|
|
|
|
|
|
/* If the exec call returns, there was an error. */
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parent: close the unused write end of the pipe. */
|
|
|
|
close(from_fd[1]);
|
|
|
|
|
|
|
|
if (pid_of_command == -1) {
|
2020-08-01 09:34:23 +00:00
|
|
|
statusline(ALERT, _("Could not fork: %s"), strerror(errno));
|
2020-03-06 17:52:42 +00:00
|
|
|
close(from_fd[0]);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
statusbar(_("Executing..."));
|
|
|
|
|
|
|
|
/* If the command starts with "|", pipe buffer or region to the command. */
|
|
|
|
if (should_pipe) {
|
|
|
|
linestruct *was_cutbuffer = cutbuffer;
|
2020-03-29 16:02:02 +00:00
|
|
|
bool whole_buffer = FALSE;
|
|
|
|
|
2020-03-06 17:52:42 +00:00
|
|
|
cutbuffer = NULL;
|
|
|
|
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
|
|
if (ISSET(MULTIBUFFER)) {
|
|
|
|
openfile = openfile->prev;
|
|
|
|
if (openfile->mark)
|
2020-03-30 11:51:15 +00:00
|
|
|
copy_marked_region();
|
2020-03-29 16:02:02 +00:00
|
|
|
else
|
|
|
|
whole_buffer = TRUE;
|
2020-03-06 17:52:42 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
2020-03-11 13:18:06 +00:00
|
|
|
/* TRANSLATORS: This one goes with Undid/Redid messages. */
|
|
|
|
add_undo(COUPLE_BEGIN, N_("filtering"));
|
2020-03-06 17:52:42 +00:00
|
|
|
if (openfile->mark == NULL) {
|
|
|
|
openfile->current = openfile->filetop;
|
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
|
|
|
add_undo(CUT, NULL);
|
2020-03-30 14:44:55 +00:00
|
|
|
do_snip(openfile->mark != NULL, openfile->mark == NULL, FALSE);
|
2020-04-30 14:47:10 +00:00
|
|
|
openfile->filetop->has_anchor = FALSE;
|
2020-03-06 17:52:42 +00:00
|
|
|
update_undo(CUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create a separate process for piping the data to the command. */
|
|
|
|
if (fork() == 0) {
|
2020-03-29 16:02:02 +00:00
|
|
|
send_data(whole_buffer ? openfile->filetop : cutbuffer, to_fd[1]);
|
2020-03-06 17:52:42 +00:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
close(to_fd[0]);
|
|
|
|
close(to_fd[1]);
|
|
|
|
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
|
|
if (ISSET(MULTIBUFFER))
|
|
|
|
openfile = openfile->next;
|
|
|
|
#endif
|
|
|
|
free_lines(cutbuffer);
|
|
|
|
cutbuffer = was_cutbuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Re-enable interpretation of the special control keys so that we get
|
|
|
|
* SIGINT when Ctrl-C is pressed. */
|
|
|
|
enable_kb_interrupt();
|
|
|
|
|
|
|
|
/* Set up a signal handler so that ^C will terminate the forked process. */
|
|
|
|
newaction.sa_handler = cancel_the_command;
|
|
|
|
newaction.sa_flags = 0;
|
|
|
|
sigaction(SIGINT, &newaction, &oldaction);
|
|
|
|
|
|
|
|
stream = fdopen(from_fd[0], "rb");
|
|
|
|
if (stream == NULL)
|
|
|
|
statusline(ALERT, _("Failed to open pipe: %s"), strerror(errno));
|
|
|
|
else
|
|
|
|
read_file(stream, 0, "pipe", TRUE);
|
|
|
|
|
|
|
|
if (should_pipe && !ISSET(MULTIBUFFER))
|
|
|
|
add_undo(COUPLE_END, N_("filtering"));
|
|
|
|
|
|
|
|
/* Wait for the external command (and possibly data sender) to terminate. */
|
|
|
|
wait(NULL);
|
|
|
|
if (should_pipe)
|
|
|
|
wait(NULL);
|
|
|
|
|
|
|
|
/* Restore the original handler for SIGINT. */
|
|
|
|
sigaction(SIGINT, &oldaction, NULL);
|
|
|
|
|
|
|
|
/* Restore the terminal to its desired state, and disable
|
|
|
|
* interpretation of the special control keys again. */
|
|
|
|
terminal_init();
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
#endif /* NANO_TINY */
|
|
|
|
|
2016-10-23 18:06:45 +00:00
|
|
|
/* Insert a file into the current buffer, or into a new buffer when
|
|
|
|
* the MULTIBUFFER flag is set. */
|
2020-05-22 12:28:42 +00:00
|
|
|
void do_insertfile(bool execute)
|
2005-07-08 02:47:05 +00:00
|
|
|
{
|
2019-05-08 17:17:16 +00:00
|
|
|
int response;
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *msg;
|
2019-10-13 10:24:27 +00:00
|
|
|
char *given = copy_of("");
|
2020-01-16 15:16:31 +00:00
|
|
|
/* The last answer the user typed at the status-bar prompt. */
|
2020-05-19 18:20:10 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
|
|
bool was_multibuffer = ISSET(MULTIBUFFER);
|
|
|
|
#endif
|
2014-06-20 15:35:26 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-02 15:09:22 +00:00
|
|
|
format_type was_fmt = openfile->fmt;
|
2007-08-16 02:34:23 +00:00
|
|
|
#endif
|
2001-07-11 02:08:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Display newlines in filenames as ^J. */
|
|
|
|
as_an_at = FALSE;
|
2016-12-20 18:27:41 +00:00
|
|
|
|
2020-05-22 09:46:30 +00:00
|
|
|
/* Reset the flag that is set by the Spell Checker and Linter and such. */
|
|
|
|
ran_a_tool = FALSE;
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (execute) {
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(MULTIBUFFER))
|
2018-08-29 18:05:37 +00:00
|
|
|
/* TRANSLATORS: The next six messages are prompts. */
|
2017-12-29 18:27:33 +00:00
|
|
|
msg = _("Command to execute in new buffer");
|
|
|
|
else
|
2005-07-08 02:47:05 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
msg = _("Command to execute");
|
|
|
|
} else
|
2017-11-12 19:08:28 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
{
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(MULTIBUFFER))
|
2018-08-19 10:35:15 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if ISSET(NO_CONVERT)
|
|
|
|
msg = _("File to read unconverted into new buffer [from %s]");
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
msg = _("File to read into new buffer [from %s]");
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2017-03-23 21:03:21 +00:00
|
|
|
#endif
|
2018-08-19 10:35:15 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if ISSET(NO_CONVERT)
|
|
|
|
msg = _("File to insert unconverted [from %s]");
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
msg = _("File to insert [from %s]");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2004-02-06 21:20:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
present_path = mallocstrcpy(present_path, "./");
|
2016-04-30 19:22:16 +00:00
|
|
|
|
2020-06-20 15:15:40 +00:00
|
|
|
response = do_prompt(
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-05-22 08:04:33 +00:00
|
|
|
execute ? MEXECUTE :
|
2004-11-25 04:39:07 +00:00
|
|
|
#endif
|
2019-05-08 17:32:30 +00:00
|
|
|
MINSERTFILE, given,
|
2017-10-29 09:30:01 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-05-08 17:32:30 +00:00
|
|
|
execute ? &execute_history :
|
2017-10-29 09:30:01 +00:00
|
|
|
#endif
|
2019-05-08 17:32:30 +00:00
|
|
|
NULL, edit_refresh, msg,
|
2017-10-29 20:08:07 +00:00
|
|
|
#ifdef ENABLE_OPERATINGDIR
|
2019-05-08 17:32:30 +00:00
|
|
|
operating_dir != NULL ? operating_dir :
|
2005-07-08 02:47:05 +00:00
|
|
|
#endif
|
2019-05-08 17:32:30 +00:00
|
|
|
"./");
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If we're in multibuffer mode and the filename or command is
|
|
|
|
* blank, open a new buffer instead of canceling. */
|
2019-05-08 17:17:16 +00:00
|
|
|
if (response == -1 || (response == -2 && !ISSET(MULTIBUFFER))) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Cancelled"));
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
ssize_t was_current_lineno = openfile->current->lineno;
|
|
|
|
size_t was_current_x = openfile->current_x;
|
2017-05-08 17:08:23 +00:00
|
|
|
#if !defined(NANO_TINY) || defined(ENABLE_BROWSER) || defined(ENABLE_MULTIBUFFER)
|
2019-05-08 17:17:16 +00:00
|
|
|
functionptrtype func = func_from_key(&response);
|
2015-12-05 11:38:26 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
given = mallocstrcpy(given, answer);
|
2004-11-25 04:39:07 +00:00
|
|
|
|
2020-05-22 09:46:30 +00:00
|
|
|
if (ran_a_tool)
|
|
|
|
break;
|
|
|
|
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
if (func == flip_newbuffer) {
|
|
|
|
/* Allow toggling only when not in view mode. */
|
|
|
|
if (!ISSET(VIEW_MODE))
|
|
|
|
TOGGLE(MULTIBUFFER);
|
|
|
|
else
|
|
|
|
beep();
|
|
|
|
continue;
|
|
|
|
}
|
2002-06-28 22:45:14 +00:00
|
|
|
#endif
|
2017-05-08 11:20:07 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-08-19 10:35:15 +00:00
|
|
|
if (func == flip_convert) {
|
|
|
|
TOGGLE(NO_CONVERT);
|
|
|
|
continue;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
if (func == flip_execute) {
|
|
|
|
execute = !execute;
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-02 13:07:04 +00:00
|
|
|
if (func == flip_pipe) {
|
|
|
|
add_or_remove_pipe_symbol_from_answer();
|
|
|
|
given = mallocstrcpy(given, answer);
|
|
|
|
continue;
|
|
|
|
}
|
2017-05-08 11:20:07 +00:00
|
|
|
#endif
|
2017-05-08 17:08:23 +00:00
|
|
|
#ifdef ENABLE_BROWSER
|
2020-01-26 15:36:23 +00:00
|
|
|
if (func == to_files) {
|
2020-07-04 15:43:16 +00:00
|
|
|
char *chosen = browse_in(answer);
|
2004-11-25 04:39:07 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If no file was chosen, go back to the prompt. */
|
|
|
|
if (chosen == NULL)
|
|
|
|
continue;
|
2004-11-25 04:39:07 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(answer);
|
|
|
|
answer = chosen;
|
2019-05-08 17:17:16 +00:00
|
|
|
response = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2018-05-23 09:57:55 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If we don't have a file yet, go back to the prompt. */
|
2019-05-08 17:17:16 +00:00
|
|
|
if (response != 0 && (!ISSET(MULTIBUFFER) || response != -2))
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
2004-11-25 04:39:07 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (execute) {
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When in multibuffer mode, first open a blank buffer. */
|
|
|
|
if (ISSET(MULTIBUFFER))
|
2018-03-21 10:36:00 +00:00
|
|
|
open_buffer("", TRUE);
|
2005-07-09 04:42:47 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the command is not empty, execute it and read its output
|
|
|
|
* into the buffer, and add the command to the history list. */
|
|
|
|
if (*answer != '\0') {
|
|
|
|
execute_command(answer);
|
2017-10-29 18:42:12 +00:00
|
|
|
#ifdef ENABLE_HISTORIES
|
2017-12-29 18:27:33 +00:00
|
|
|
update_history(&execute_history, answer);
|
2017-10-03 19:39:09 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2006-07-13 03:06:36 +00:00
|
|
|
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If this is a new buffer, put the cursor at the top. */
|
|
|
|
if (ISSET(MULTIBUFFER)) {
|
2019-03-21 16:23:49 +00:00
|
|
|
openfile->current = openfile->filetop;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = 0;
|
|
|
|
openfile->placewewant = 0;
|
|
|
|
|
|
|
|
set_modified();
|
|
|
|
}
|
2005-07-08 02:47:05 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
} else
|
2006-07-13 03:06:36 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2017-12-29 18:27:33 +00:00
|
|
|
{
|
|
|
|
/* Make sure the specified path is tilde-expanded. */
|
|
|
|
answer = free_and_assign(answer, real_dir_from_tilde(answer));
|
2005-07-09 04:42:47 +00:00
|
|
|
|
2018-03-21 10:36:00 +00:00
|
|
|
/* Read the file into a new buffer or into current buffer. */
|
|
|
|
open_buffer(answer, ISSET(MULTIBUFFER));
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2001-10-22 03:15:31 +00:00
|
|
|
|
2017-05-01 18:20:34 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(MULTIBUFFER)) {
|
2017-10-29 18:42:12 +00:00
|
|
|
#ifdef ENABLE_HISTORIES
|
2018-09-30 11:27:08 +00:00
|
|
|
if (ISSET(POSITIONLOG)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
ssize_t priorline, priorcol;
|
2014-06-20 16:33:12 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!execute)
|
2014-06-20 16:33:12 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
if (has_old_position(answer, &priorline, &priorcol))
|
|
|
|
do_gotolinecolumn(priorline, priorcol, FALSE, FALSE);
|
|
|
|
}
|
2017-11-13 18:58:29 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Update title bar and color info for this new buffer. */
|
|
|
|
prepare_for_display();
|
|
|
|
} else
|
2017-05-01 18:20:34 +00:00
|
|
|
#endif /* ENABLE_MULTIBUFFER */
|
2017-12-29 18:27:33 +00:00
|
|
|
{
|
|
|
|
/* If the file actually changed, mark it as modified. */
|
|
|
|
if (openfile->current->lineno != was_current_lineno ||
|
|
|
|
openfile->current_x != was_current_x)
|
|
|
|
set_modified();
|
2017-11-12 17:45:53 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Ensure that the buffer retains the format that it had. */
|
2019-10-02 15:09:22 +00:00
|
|
|
openfile->fmt = was_fmt;
|
2017-11-12 17:45:53 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2001-07-11 02:08:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2005-07-08 02:47:05 +00:00
|
|
|
}
|
2016-05-23 18:44:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(given);
|
2020-05-19 18:20:10 +00:00
|
|
|
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
|
|
if (was_multibuffer)
|
|
|
|
SET(MULTIBUFFER);
|
|
|
|
else
|
|
|
|
UNSET(MULTIBUFFER);
|
|
|
|
#endif
|
2002-02-15 19:17:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-01 14:04:51 +00:00
|
|
|
/* If the current mode of operation allows it, go insert a file. */
|
2005-07-08 02:47:05 +00:00
|
|
|
void do_insertfile_void(void)
|
2001-07-11 02:08:33 +00:00
|
|
|
{
|
2019-10-09 16:58:30 +00:00
|
|
|
if (!in_restricted_mode())
|
2020-05-22 12:28:42 +00:00
|
|
|
do_insertfile(FALSE);
|
2001-07-11 02:08:33 +00:00
|
|
|
}
|
|
|
|
|
2020-05-22 12:28:42 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
/* If the current mode of operation allows it, go prompt for a command. */
|
|
|
|
void do_execute(void)
|
|
|
|
{
|
|
|
|
if (!in_restricted_mode())
|
|
|
|
do_insertfile(TRUE);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* For the given bare path (or path plus filename), return the canonical,
|
|
|
|
* absolute path (plus filename) when the path exists, and NULL when not. */
|
2002-09-13 18:14:04 +00:00
|
|
|
char *get_full_path(const char *origpath)
|
2001-07-11 02:08:33 +00:00
|
|
|
{
|
2019-09-17 15:38:42 +00:00
|
|
|
char *allocation, *here, *target, *last_slash;
|
|
|
|
char *just_filename = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
int attempts = 0;
|
|
|
|
struct stat fileinfo;
|
|
|
|
bool path_only;
|
|
|
|
|
|
|
|
if (origpath == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
2019-09-17 14:46:11 +00:00
|
|
|
allocation = charalloc(PATH_MAX + 1);
|
|
|
|
here = getcwd(allocation, PATH_MAX + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If getting the current directory failed, go up one level and try again,
|
|
|
|
* until we find an existing directory, and use that as the current one. */
|
2019-09-17 14:39:21 +00:00
|
|
|
while (here == NULL && attempts < 20) {
|
2017-12-29 18:27:33 +00:00
|
|
|
IGNORE_CALL_RESULT(chdir(".."));
|
2019-09-17 14:46:11 +00:00
|
|
|
here = getcwd(allocation, PATH_MAX + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
attempts++;
|
2016-12-16 20:28:28 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If we found a directory, make sure its path ends in a slash. */
|
2019-09-17 14:39:21 +00:00
|
|
|
if (here != NULL) {
|
|
|
|
if (strcmp(here, "/") != 0) {
|
|
|
|
here = charealloc(here, strlen(here) + 2);
|
|
|
|
strcat(here, "/");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-10-13 10:24:27 +00:00
|
|
|
here = copy_of("");
|
2019-09-17 14:46:11 +00:00
|
|
|
free(allocation);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 14:39:21 +00:00
|
|
|
target = real_dir_from_tilde(origpath);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-30 08:56:37 +00:00
|
|
|
/* Determine whether the target path refers to a directory. If statting
|
2019-09-17 15:38:42 +00:00
|
|
|
* target fails, however, assume that it refers to a new, unsaved file. */
|
2019-09-17 14:39:21 +00:00
|
|
|
path_only = (stat(target, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If the target is a directory, make sure its path ends in a slash. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (path_only) {
|
2019-09-17 15:38:42 +00:00
|
|
|
size_t length = strlen(target);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
if (target[length - 1] != '/') {
|
|
|
|
target = charealloc(target, length + 2);
|
2019-09-17 14:39:21 +00:00
|
|
|
strcat(target, "/");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2001-09-19 03:19:43 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 14:39:21 +00:00
|
|
|
last_slash = strrchr(target, '/');
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If the target path does not contain a slash, then it is a bare filename
|
|
|
|
* and must therefore be located in the working directory. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (last_slash == NULL) {
|
2019-09-17 14:39:21 +00:00
|
|
|
just_filename = target;
|
|
|
|
target = here;
|
2005-02-09 18:56:21 +00:00
|
|
|
} else {
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If target contains a filename, separate the two. */
|
|
|
|
if (!path_only) {
|
2019-10-13 10:24:27 +00:00
|
|
|
just_filename = copy_of(last_slash + 1);
|
2019-09-17 15:38:42 +00:00
|
|
|
*(last_slash + 1) = '\0';
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If we can't change to the target directory, give up. Otherwise,
|
|
|
|
* get the canonical path to this target directory. */
|
2019-09-17 14:39:21 +00:00
|
|
|
if (chdir(target) == -1) {
|
|
|
|
free(target);
|
|
|
|
target = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2019-09-17 14:39:21 +00:00
|
|
|
free(target);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 14:46:11 +00:00
|
|
|
allocation = charalloc(PATH_MAX + 1);
|
|
|
|
target = getcwd(allocation, PATH_MAX + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If we got a result, make sure it ends in a slash.
|
|
|
|
* Otherwise, ensure that we return NULL. */
|
2019-09-17 14:39:21 +00:00
|
|
|
if (target != NULL) {
|
|
|
|
if (strcmp(target, "/") != 0) {
|
|
|
|
target = charealloc(target, strlen(target) + 2);
|
|
|
|
strcat(target, "/");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
path_only = TRUE;
|
2019-09-17 14:46:11 +00:00
|
|
|
free(allocation);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* Finally, go back to where we were before. We don't check
|
|
|
|
* for an error, since we can't do anything if we get one. */
|
2019-09-17 14:39:21 +00:00
|
|
|
IGNORE_CALL_RESULT(chdir(here));
|
2016-12-16 20:28:28 +00:00
|
|
|
}
|
2006-07-05 14:14:06 +00:00
|
|
|
|
2019-09-17 14:39:21 +00:00
|
|
|
free(here);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 15:38:42 +00:00
|
|
|
/* If we were given more than a bare path, concatenate the target path
|
|
|
|
* with the filename portion to get the full, absolute file path. */
|
2019-09-17 14:39:21 +00:00
|
|
|
if (!path_only && target != NULL) {
|
|
|
|
target = charealloc(target, strlen(target) + strlen(just_filename) + 1);
|
|
|
|
strcat(target, just_filename);
|
2001-09-19 03:19:43 +00:00
|
|
|
}
|
2016-02-10 12:32:43 +00:00
|
|
|
|
2019-09-17 14:39:21 +00:00
|
|
|
free(just_filename);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-09-17 14:39:21 +00:00
|
|
|
return target;
|
2001-07-11 02:08:33 +00:00
|
|
|
}
|
2002-02-15 19:17:02 +00:00
|
|
|
|
2019-10-09 12:36:28 +00:00
|
|
|
/* Check whether the given path refers to a directory that is writable.
|
|
|
|
* Return the absolute form of the path on success, and NULL on failure. */
|
2002-09-13 18:14:04 +00:00
|
|
|
char *check_writable_directory(const char *path)
|
2002-09-06 20:35:28 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *full_path = get_full_path(path);
|
2002-02-15 19:17:02 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (full_path == NULL)
|
|
|
|
return NULL;
|
2002-02-16 20:03:44 +00:00
|
|
|
|
2019-10-09 12:36:28 +00:00
|
|
|
if (full_path[strlen(full_path) - 1] != '/' || access(full_path, W_OK) != 0) {
|
2017-12-29 18:27:33 +00:00
|
|
|
free(full_path);
|
|
|
|
return NULL;
|
|
|
|
}
|
2002-02-15 19:17:02 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return full_path;
|
2002-02-15 19:17:02 +00:00
|
|
|
}
|
|
|
|
|
2019-10-09 12:20:29 +00:00
|
|
|
/* Create, safely, a temporary file in the standard temp directory.
|
|
|
|
* On success, return the malloc()ed filename, plus the corresponding
|
|
|
|
* file stream opened in read-write mode. On error, return NULL. */
|
|
|
|
char *safe_tempfile(FILE **stream)
|
2002-09-06 20:35:28 +00:00
|
|
|
{
|
2019-10-09 12:20:29 +00:00
|
|
|
const char *env_dir = getenv("TMPDIR");
|
|
|
|
char *tempdir = NULL, *tempfile_name = NULL;
|
2019-02-10 16:04:55 +00:00
|
|
|
int fd;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-02-10 16:04:55 +00:00
|
|
|
/* Get the absolute path for the first directory among $TMPDIR
|
|
|
|
* and P_tmpdir that is writable, otherwise use /tmp/. */
|
2019-10-09 12:20:29 +00:00
|
|
|
if (env_dir != NULL)
|
|
|
|
tempdir = check_writable_directory(env_dir);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-10-09 12:20:29 +00:00
|
|
|
if (tempdir == NULL)
|
|
|
|
tempdir = check_writable_directory(P_tmpdir);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-10-09 12:20:29 +00:00
|
|
|
if (tempdir == NULL)
|
2019-10-13 10:24:27 +00:00
|
|
|
tempdir = copy_of("/tmp/");
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-10-09 12:20:29 +00:00
|
|
|
tempfile_name = charealloc(tempdir, strlen(tempdir) + 12);
|
2019-02-10 16:17:35 +00:00
|
|
|
strcat(tempfile_name, "nano.XXXXXX");
|
2002-09-13 18:14:04 +00:00
|
|
|
|
2019-02-10 16:17:35 +00:00
|
|
|
fd = mkstemp(tempfile_name);
|
2005-06-05 19:33:27 +00:00
|
|
|
|
2019-10-09 12:20:29 +00:00
|
|
|
if (fd == -1) {
|
2019-02-10 16:17:35 +00:00
|
|
|
free(tempfile_name);
|
2019-10-09 12:20:29 +00:00
|
|
|
return NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2002-09-13 18:14:04 +00:00
|
|
|
|
2019-10-09 12:20:29 +00:00
|
|
|
*stream = fdopen(fd, "r+b");
|
2005-06-05 19:33:27 +00:00
|
|
|
|
2019-02-10 16:17:35 +00:00
|
|
|
return tempfile_name;
|
2002-02-15 19:17:02 +00:00
|
|
|
}
|
2001-09-19 03:19:43 +00:00
|
|
|
|
2017-10-29 20:08:07 +00:00
|
|
|
#ifdef ENABLE_OPERATINGDIR
|
2017-08-05 19:30:27 +00:00
|
|
|
/* Change to the specified operating directory, when it's valid. */
|
2002-09-13 18:14:04 +00:00
|
|
|
void init_operating_dir(void)
|
|
|
|
{
|
2018-12-27 20:08:57 +00:00
|
|
|
char *target = get_full_path(operating_dir);
|
2002-09-13 18:14:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the operating directory is inaccessible, fail. */
|
2018-12-27 20:08:57 +00:00
|
|
|
if (target == NULL || chdir(target) == -1)
|
|
|
|
die(_("Invalid operating directory: %s\n"), operating_dir);
|
2016-12-16 20:28:28 +00:00
|
|
|
|
2018-12-27 20:08:57 +00:00
|
|
|
free(operating_dir);
|
2018-12-27 20:17:37 +00:00
|
|
|
operating_dir = charealloc(target, strlen(target) + 1);
|
2002-09-13 18:14:04 +00:00
|
|
|
}
|
|
|
|
|
2017-08-13 08:04:31 +00:00
|
|
|
/* Check whether the given path is outside of the operating directory.
|
2020-06-21 07:05:41 +00:00
|
|
|
* Return TRUE if it is, and FALSE otherwise. If tabbing is TRUE,
|
2017-08-13 08:04:31 +00:00
|
|
|
* incomplete names that can grow into matches for the operating directory
|
|
|
|
* are considered to be inside, so that tab completion will work. */
|
2020-06-21 07:05:41 +00:00
|
|
|
bool outside_of_confinement(const char *somepath, bool tabbing)
|
2001-09-19 03:19:43 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
bool is_inside, begins_to_be;
|
2020-06-21 07:05:41 +00:00
|
|
|
char *fullpath;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If no operating directory is set, there is nothing to check. */
|
|
|
|
if (operating_dir == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
2020-06-21 07:05:41 +00:00
|
|
|
fullpath = get_full_path(somepath);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-21 07:05:41 +00:00
|
|
|
/* When we can't get an absolute path, it means some directory in the path
|
|
|
|
* doesn't exist or is unreadable. When not doing tab completion, somepath
|
|
|
|
* is what the user typed somewhere. We don't want to report a non-existent
|
|
|
|
* directory as being outside the operating directory, so we return FALSE.
|
|
|
|
* When the user is doing tab completion, then somepath exists but is not
|
|
|
|
* executable. So we say it is outside the operating directory. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (fullpath == NULL)
|
2020-06-21 07:05:41 +00:00
|
|
|
return tabbing;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
is_inside = (strstr(fullpath, operating_dir) == fullpath);
|
2020-06-21 07:05:41 +00:00
|
|
|
begins_to_be = (tabbing && strstr(operating_dir, fullpath) == operating_dir);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(fullpath);
|
|
|
|
|
|
|
|
return (!is_inside && !begins_to_be);
|
2001-09-19 03:19:43 +00:00
|
|
|
}
|
2001-07-11 02:08:33 +00:00
|
|
|
#endif
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-08-05 19:55:03 +00:00
|
|
|
/* Transform the specified backup directory to an absolute path,
|
|
|
|
* and verify that it is usable. */
|
2004-02-28 16:24:31 +00:00
|
|
|
void init_backup_dir(void)
|
|
|
|
{
|
2018-12-27 20:08:57 +00:00
|
|
|
char *target = get_full_path(backup_dir);
|
2017-08-05 19:55:03 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If we can't get an absolute path (which means it doesn't exist or
|
2017-12-29 20:35:14 +00:00
|
|
|
* isn't accessible), or it's not a directory, fail. */
|
2018-12-27 20:08:57 +00:00
|
|
|
if (target == NULL || target[strlen(target) - 1] != '/')
|
|
|
|
die(_("Invalid backup directory: %s\n"), backup_dir);
|
2017-08-05 19:55:03 +00:00
|
|
|
|
2018-12-27 20:08:57 +00:00
|
|
|
free(backup_dir);
|
2018-12-27 20:17:37 +00:00
|
|
|
backup_dir = charealloc(target, strlen(target) + 1);
|
2004-02-28 16:24:31 +00:00
|
|
|
}
|
2020-07-28 16:51:15 +00:00
|
|
|
#endif
|
2004-02-28 16:24:31 +00:00
|
|
|
|
2019-10-18 11:25:38 +00:00
|
|
|
/* Read all data from inn, and write it to out. File inn must be open for
|
|
|
|
* reading, and out for writing. Return 0 on success, a negative number on
|
|
|
|
* read error, and a positive number on write error. File inn is always
|
|
|
|
* closed by this function, out is closed only if close_out is true. */
|
2017-04-04 07:29:31 +00:00
|
|
|
int copy_file(FILE *inn, FILE *out, bool close_out)
|
2003-12-24 08:03:54 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
int retval = 0;
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
size_t charsread;
|
|
|
|
int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
|
2005-04-19 20:13:13 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
do {
|
|
|
|
charsread = fread(buf, sizeof(char), BUFSIZ, inn);
|
|
|
|
if (charsread == 0 && ferror(inn)) {
|
|
|
|
retval = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (fwrite(buf, sizeof(char), charsread, out) < charsread) {
|
2019-10-18 11:25:38 +00:00
|
|
|
retval = 2;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (charsread > 0);
|
|
|
|
|
|
|
|
if (fclose(inn) == EOF)
|
2019-10-18 11:25:38 +00:00
|
|
|
retval = -3;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (flush_out_fnc(out) == EOF)
|
2019-10-18 11:25:38 +00:00
|
|
|
retval = 4;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
return retval;
|
2003-12-24 08:03:54 +00:00
|
|
|
}
|
|
|
|
|
2014-06-09 20:26:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-07-13 08:31:33 +00:00
|
|
|
/* Create a backup of an existing file. If the user did not request backups,
|
|
|
|
* make a temporary one (trying first in the directory of the original file,
|
|
|
|
* then in the user's home directory). Return TRUE if the save can proceed. */
|
2020-07-15 08:28:22 +00:00
|
|
|
bool make_backup_of(char *realname)
|
2020-07-13 08:31:33 +00:00
|
|
|
{
|
2020-07-13 08:47:06 +00:00
|
|
|
FILE *original = NULL, *backup_file = NULL;
|
2020-07-17 14:22:03 +00:00
|
|
|
int creation_flags, descriptor, verdict;
|
2020-07-14 10:55:37 +00:00
|
|
|
static struct timespec filetime[2];
|
|
|
|
bool second_attempt = FALSE;
|
2020-07-15 08:28:22 +00:00
|
|
|
char *backupname = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
/* Remember the original file's access and modification times. */
|
|
|
|
filetime[0].tv_sec = openfile->statinfo->st_atime;
|
|
|
|
filetime[1].tv_sec = openfile->statinfo->st_mtime;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
statusbar(_("Making backup..."));
|
2002-06-28 22:45:14 +00:00
|
|
|
|
2020-07-15 08:15:20 +00:00
|
|
|
/* If no backup directory was specified, we make a simple backup
|
|
|
|
* by appending a tilde to the original file name. Otherwise,
|
|
|
|
* we create a numbered backup in the specified directory. */
|
|
|
|
if (backup_dir == NULL) {
|
2020-07-15 08:28:22 +00:00
|
|
|
backupname = charalloc(strlen(realname) + 2);
|
|
|
|
sprintf(backupname, "%s~", realname);
|
2020-07-15 08:15:20 +00:00
|
|
|
} else {
|
|
|
|
char *thename = get_full_path(realname);
|
|
|
|
|
|
|
|
/* If we have a valid absolute path, replace each slash
|
|
|
|
* in this full path with an exclamation mark. Otherwise,
|
|
|
|
* just use the file-name portion of the given path. */
|
|
|
|
if (thename) {
|
|
|
|
for (int i = 0; thename[i] != '\0'; i++)
|
|
|
|
if (thename[i] == '/')
|
|
|
|
thename[i] = '!';
|
|
|
|
} else
|
|
|
|
thename = copy_of(tail(realname));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-07-15 08:28:22 +00:00
|
|
|
backupname = charalloc(strlen(backup_dir) + strlen(thename) + 1);
|
|
|
|
sprintf(backupname, "%s%s", backup_dir, thename);
|
2020-07-15 08:15:20 +00:00
|
|
|
free(thename);
|
2020-05-28 17:29:08 +00:00
|
|
|
|
2020-07-15 08:28:22 +00:00
|
|
|
thename = get_next_filename(backupname, "~");
|
|
|
|
free(backupname);
|
|
|
|
backupname = thename;
|
2020-05-25 17:49:17 +00:00
|
|
|
|
2020-07-15 08:15:20 +00:00
|
|
|
/* If all numbered backup names are taken, the user must
|
|
|
|
* be fond of backups. Thus, without one, do not go on. */
|
2020-07-15 08:28:22 +00:00
|
|
|
if (*backupname == '\0') {
|
2020-07-15 08:15:20 +00:00
|
|
|
statusline(ALERT, _("Too many existing backup files"));
|
2020-07-15 08:28:22 +00:00
|
|
|
free(backupname);
|
2020-07-15 08:15:20 +00:00
|
|
|
return FALSE;
|
2004-02-28 16:24:31 +00:00
|
|
|
}
|
2020-07-15 08:15:20 +00:00
|
|
|
}
|
2002-06-28 22:45:14 +00:00
|
|
|
|
2020-07-15 08:15:20 +00:00
|
|
|
/* Now first try to delete an existing backup file. */
|
2020-07-15 08:28:22 +00:00
|
|
|
if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP))
|
2020-07-17 14:24:53 +00:00
|
|
|
goto problem;
|
2010-04-07 05:48:24 +00:00
|
|
|
|
2020-07-17 14:22:03 +00:00
|
|
|
creation_flags = O_WRONLY|O_CREAT|(ISSET(INSECURE_BACKUP) ? O_TRUNC : O_EXCL);
|
2010-06-24 14:47:00 +00:00
|
|
|
|
2020-07-15 08:15:20 +00:00
|
|
|
/* Create the backup file (or truncate the existing one). */
|
2020-07-17 14:22:03 +00:00
|
|
|
descriptor = open(backupname, creation_flags, S_IRUSR|S_IWUSR);
|
2010-04-07 05:48:24 +00:00
|
|
|
|
2020-07-17 14:24:53 +00:00
|
|
|
retry:
|
2020-07-17 14:22:03 +00:00
|
|
|
if (descriptor >= 0)
|
|
|
|
backup_file = fdopen(descriptor, "wb");
|
2017-08-15 11:46:20 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
if (backup_file == NULL)
|
2020-07-17 14:24:53 +00:00
|
|
|
goto problem;
|
2005-04-19 21:47:01 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
/* Try to change owner and group to those of the original file;
|
2020-07-28 08:05:52 +00:00
|
|
|
* ignore permission errors, as a normal user cannot change the owner. */
|
|
|
|
if (fchown(descriptor, openfile->statinfo->st_uid,
|
|
|
|
openfile->statinfo->st_gid) < 0 && errno != EPERM) {
|
|
|
|
fclose(backup_file);
|
|
|
|
goto problem;
|
|
|
|
}
|
2010-05-23 04:30:23 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
/* Set the backup's permissions to those of the original file.
|
|
|
|
* It is not a security issue if this fails, as we have created
|
|
|
|
* the file with just read and write permission for the owner. */
|
2020-07-28 08:05:52 +00:00
|
|
|
if (fchmod(descriptor, openfile->statinfo->st_mode) < 0 && errno != EPERM) {
|
|
|
|
fclose(backup_file);
|
|
|
|
goto problem;
|
|
|
|
}
|
2002-06-28 22:45:14 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
original = fopen(realname, "rb");
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-17 14:52:47 +00:00
|
|
|
/* If opening succeeded, copy the existing file to the backup. */
|
|
|
|
if (original != NULL)
|
|
|
|
verdict = copy_file(original, backup_file, FALSE);
|
2019-10-18 10:31:31 +00:00
|
|
|
|
2020-07-17 14:52:47 +00:00
|
|
|
if (original == NULL || verdict < 0) {
|
2020-07-17 14:29:10 +00:00
|
|
|
warn_and_briefly_pause(_("Cannot read original file"));
|
2020-07-14 11:04:34 +00:00
|
|
|
fclose(backup_file);
|
2020-07-17 14:29:10 +00:00
|
|
|
goto failure;
|
2020-07-13 08:47:06 +00:00
|
|
|
} else if (verdict > 0) {
|
|
|
|
fclose(backup_file);
|
2020-07-17 14:24:53 +00:00
|
|
|
goto problem;
|
2020-07-13 08:47:06 +00:00
|
|
|
}
|
2010-06-24 14:47:00 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
/* Since this backup is a newly created file, explicitly sync it to
|
|
|
|
* permanent storage before starting to write out the actual file. */
|
2020-07-17 16:33:10 +00:00
|
|
|
if (fflush(backup_file) != 0 || fsync(fileno(backup_file)) != 0) {
|
|
|
|
fclose(backup_file);
|
2020-07-17 14:24:53 +00:00
|
|
|
goto problem;
|
2020-07-17 16:33:10 +00:00
|
|
|
}
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
/* Set the backup's timestamps to those of the original file.
|
|
|
|
* Failure is unimportant: saving the file apparently worked. */
|
2020-07-17 14:22:03 +00:00
|
|
|
IGNORE_CALL_RESULT(futimens(descriptor, filetime));
|
2005-05-29 02:22:55 +00:00
|
|
|
|
2020-07-15 08:28:22 +00:00
|
|
|
if (fclose(backup_file) == 0) {
|
|
|
|
free(backupname);
|
2020-07-13 08:47:06 +00:00
|
|
|
return TRUE;
|
2020-07-15 08:28:22 +00:00
|
|
|
}
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-17 14:24:53 +00:00
|
|
|
problem:
|
2020-07-13 08:47:06 +00:00
|
|
|
get_homedir();
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
/* If the first attempt of copying the file failed, try again to HOME. */
|
|
|
|
if (!second_attempt && homedir) {
|
2020-07-15 08:28:22 +00:00
|
|
|
unlink(backupname);
|
|
|
|
free(backupname);
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-15 08:15:20 +00:00
|
|
|
warn_and_briefly_pause(_("Cannot make regular backup"));
|
|
|
|
warn_and_briefly_pause(_("Trying again in your home directory"));
|
|
|
|
currmenu = MMOST;
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-15 08:28:22 +00:00
|
|
|
backupname = charalloc(strlen(homedir) + strlen(tail(realname)) + 9);
|
|
|
|
sprintf(backupname, "%s/%s~XXXXXX", homedir, tail(realname));
|
2020-07-14 10:55:37 +00:00
|
|
|
|
2020-07-17 14:22:03 +00:00
|
|
|
descriptor = mkstemp(backupname);
|
2020-07-14 10:55:37 +00:00
|
|
|
backup_file = NULL;
|
|
|
|
|
2020-07-13 08:47:06 +00:00
|
|
|
second_attempt = TRUE;
|
2020-07-17 14:24:53 +00:00
|
|
|
goto retry;
|
2020-07-17 15:13:43 +00:00
|
|
|
} else
|
|
|
|
warn_and_briefly_pause(_("Cannot make backup"));
|
2020-07-13 08:47:06 +00:00
|
|
|
|
2020-07-17 14:29:10 +00:00
|
|
|
failure:
|
2020-07-16 13:01:04 +00:00
|
|
|
warn_and_briefly_pause(strerror(errno));
|
|
|
|
currmenu = MMOST;
|
2020-07-17 15:13:43 +00:00
|
|
|
free(backupname);
|
2020-07-16 13:01:04 +00:00
|
|
|
|
|
|
|
/* If both attempts failed, and it isn't because of lack of disk space,
|
|
|
|
* ask the user what to do, because if something goes wrong during the
|
|
|
|
* save of the file itself, its contents may be lost. */
|
2020-07-27 08:33:42 +00:00
|
|
|
/* TRANSLATORS: Try to keep this message at most 76 characters. */
|
2020-07-16 13:01:04 +00:00
|
|
|
if (errno != ENOSPC && do_yesno_prompt(FALSE, _("Cannot make backup; "
|
2020-07-17 15:13:43 +00:00
|
|
|
"continue and save actual file? ")) == 1)
|
2020-07-14 10:55:37 +00:00
|
|
|
return TRUE;
|
|
|
|
|
2020-07-17 15:10:46 +00:00
|
|
|
/* TRANSLATORS: The %s is the reason of failure. */
|
|
|
|
statusline(HUSH, _("Cannot make backup: %s"), strerror(errno));
|
2020-07-14 10:55:37 +00:00
|
|
|
return FALSE;
|
2020-07-13 08:31:33 +00:00
|
|
|
}
|
|
|
|
#endif /* !NANO_TINY */
|
|
|
|
|
|
|
|
/* Write the current buffer to disk. If thefile isn't NULL, we write to a
|
|
|
|
* temporary file that is already open. If tmp is TRUE (when spell checking
|
2020-07-15 08:43:15 +00:00
|
|
|
* or emergency dumping, for example), we don't make a backup and don't give
|
|
|
|
* feedback. If method is APPEND or PREPEND, it means we will be appending
|
2020-07-13 08:31:33 +00:00
|
|
|
* or prepending instead of overwriting the given file. If fullbuffer is TRUE
|
|
|
|
* and when writing normally, we set the current filename and stat info.
|
|
|
|
* Return TRUE on success, and FALSE otherwise. */
|
|
|
|
bool write_file(const char *name, FILE *thefile, bool tmp,
|
|
|
|
kind_of_writing_type method, bool fullbuffer)
|
|
|
|
{
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
bool is_existing_file;
|
|
|
|
/* Becomes TRUE when the file is non-temporary and exists. */
|
|
|
|
struct stat st;
|
|
|
|
/* The status fields filled in by statting the file. */
|
|
|
|
#endif
|
|
|
|
char *realname = real_dir_from_tilde(name);
|
|
|
|
/* The filename after tilde expansion. */
|
|
|
|
char *tempname = NULL;
|
|
|
|
/* The name of the temporary file we use when prepending. */
|
|
|
|
linestruct *line = openfile->filetop;
|
|
|
|
/* An iterator for moving through the lines of the buffer. */
|
|
|
|
size_t lineswritten = 0;
|
|
|
|
/* The number of lines written, for feedback on the status bar. */
|
|
|
|
bool retval = FALSE;
|
|
|
|
/* The return value, to become TRUE when writing has succeeded. */
|
|
|
|
|
|
|
|
#ifdef ENABLE_OPERATINGDIR
|
|
|
|
/* If we're writing a temporary file, we're probably going outside
|
|
|
|
* the operating directory, so skip the operating directory test. */
|
|
|
|
if (!tmp && outside_of_confinement(realname, FALSE)) {
|
|
|
|
statusline(ALERT, _("Can't write outside of %s"), operating_dir);
|
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
/* Check whether the file (at the end of the symlink) exists. */
|
|
|
|
is_existing_file = (!tmp) && (stat(realname, &st) != -1);
|
|
|
|
|
|
|
|
/* If we haven't statted 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 and such during backup. */
|
|
|
|
if (openfile->statinfo == NULL && is_existing_file)
|
|
|
|
stat_with_alloc(realname, &openfile->statinfo);
|
|
|
|
|
|
|
|
/* When the user requested a backup, we do this only if the file exists and
|
|
|
|
* isn't temporary AND the file has not been modified by someone else since
|
|
|
|
* we opened it (or we are appending/prepending or writing a selection). */
|
2020-07-15 08:05:21 +00:00
|
|
|
if (ISSET(MAKE_BACKUP) && is_existing_file && openfile->statinfo &&
|
2020-07-13 08:31:33 +00:00
|
|
|
(openfile->statinfo->st_mtime == st.st_mtime ||
|
|
|
|
method != OVERWRITE || openfile->mark)) {
|
2020-07-15 08:28:22 +00:00
|
|
|
if (!make_backup_of(realname))
|
2020-07-13 08:31:33 +00:00
|
|
|
goto cleanup_and_exit;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2006-04-06 05:18:23 +00:00
|
|
|
|
2019-10-16 15:16:48 +00:00
|
|
|
/* When prepending, first copy the existing file to a temporary file. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (method == PREPEND) {
|
2019-10-17 10:08:50 +00:00
|
|
|
FILE *source = fopen(realname, "rb");
|
|
|
|
FILE *target = NULL;
|
2019-10-18 09:53:47 +00:00
|
|
|
int verdict;
|
2019-10-16 15:40:52 +00:00
|
|
|
|
|
|
|
if (source == NULL) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error reading %s: %s"), realname, strerror(errno));
|
2019-10-16 14:05:40 +00:00
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2003-12-24 08:03:54 +00:00
|
|
|
|
2019-10-16 15:16:48 +00:00
|
|
|
tempname = safe_tempfile(&target);
|
2000-06-19 04:22:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (tempname == NULL) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
2020-06-26 08:24:52 +00:00
|
|
|
fclose(source);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2003-12-24 08:03:54 +00:00
|
|
|
|
2019-10-18 09:53:47 +00:00
|
|
|
verdict = copy_file(source, target, TRUE);
|
|
|
|
|
2019-10-18 11:25:38 +00:00
|
|
|
if (verdict < 0) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error reading %s: %s"), realname, strerror(errno));
|
2019-10-18 09:53:47 +00:00
|
|
|
unlink(tempname);
|
|
|
|
goto cleanup_and_exit;
|
2019-10-18 11:25:38 +00:00
|
|
|
} else if (verdict > 0) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
unlink(tempname);
|
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2005-05-31 04:28:15 +00:00
|
|
|
}
|
2002-04-22 23:52:34 +00:00
|
|
|
|
2020-03-01 18:45:02 +00:00
|
|
|
if (is_existing_file && S_ISFIFO(st.st_mode))
|
2019-05-18 17:09:02 +00:00
|
|
|
statusbar(_("Writing to FIFO..."));
|
2019-05-27 07:55:24 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2019-05-18 17:09:02 +00:00
|
|
|
|
2020-05-26 10:36:18 +00:00
|
|
|
/* When it's not a temporary file, this is where we open or create it.
|
|
|
|
* For an emergency file, access is restricted to just the owner. */
|
2019-10-18 15:10:47 +00:00
|
|
|
if (thefile == NULL) {
|
2020-05-26 10:36:18 +00:00
|
|
|
mode_t permissions = (tmp ? S_IRUSR|S_IWUSR : RW_FOR_ALL);
|
2019-10-16 08:47:44 +00:00
|
|
|
int fd;
|
2019-10-17 15:29:11 +00:00
|
|
|
|
2019-05-26 09:45:51 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-05-20 11:07:09 +00:00
|
|
|
block_sigwinch(TRUE);
|
2019-05-26 11:32:28 +00:00
|
|
|
install_handler_for_Ctrl_C();
|
2019-05-26 09:45:51 +00:00
|
|
|
#endif
|
2020-05-26 10:36:18 +00:00
|
|
|
|
2019-10-18 15:10:47 +00:00
|
|
|
/* Now open the file. Use O_EXCL for an emergency file. */
|
2017-12-29 18:27:33 +00:00
|
|
|
fd = open(realname, O_WRONLY | O_CREAT | ((method == APPEND) ?
|
2020-05-26 10:36:18 +00:00
|
|
|
O_APPEND : (tmp ? O_EXCL : O_TRUNC)), permissions);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-05-26 09:45:51 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-05-26 11:32:28 +00:00
|
|
|
restore_handler_for_Ctrl_C();
|
2019-05-20 11:07:09 +00:00
|
|
|
block_sigwinch(FALSE);
|
2019-05-26 09:45:51 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If we couldn't open the file, give up. */
|
|
|
|
if (fd == -1) {
|
2019-05-26 17:45:58 +00:00
|
|
|
if (errno == EINTR || errno == 0)
|
2019-05-26 12:17:35 +00:00
|
|
|
statusline(ALERT, _("Interrupted"));
|
|
|
|
else
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
2019-10-18 12:16:55 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (tempname != NULL)
|
|
|
|
unlink(tempname);
|
2019-10-18 12:16:55 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2005-05-28 23:32:45 +00:00
|
|
|
|
2019-10-17 10:17:09 +00:00
|
|
|
thefile = fdopen(fd, (method == APPEND) ? "ab" : "wb");
|
2002-04-22 23:52:34 +00:00
|
|
|
|
2019-10-17 10:17:09 +00:00
|
|
|
if (thefile == NULL) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
close(fd);
|
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
|
|
|
}
|
2002-09-13 18:14:04 +00:00
|
|
|
|
2019-06-12 07:46:19 +00:00
|
|
|
if (!tmp)
|
|
|
|
statusbar(_("Writing..."));
|
2019-05-18 17:09:02 +00:00
|
|
|
|
2020-04-01 14:05:45 +00:00
|
|
|
while (TRUE) {
|
|
|
|
size_t data_len = strlen(line->data);
|
|
|
|
size_t wrote;
|
2002-06-13 00:40:19 +00:00
|
|
|
|
2018-11-05 08:38:07 +00:00
|
|
|
/* Decode LFs as the NULs that they are, before writing to disk. */
|
2020-04-15 15:20:03 +00:00
|
|
|
recode_LF_to_NUL(line->data);
|
2002-06-13 00:40:19 +00:00
|
|
|
|
2020-04-01 14:05:45 +00:00
|
|
|
wrote = fwrite(line->data, sizeof(char), data_len, thefile);
|
2002-06-13 00:40:19 +00:00
|
|
|
|
2018-11-05 08:38:07 +00:00
|
|
|
/* Re-encode any embedded NULs as LFs. */
|
2020-04-15 15:20:03 +00:00
|
|
|
recode_NUL_to_LF(line->data, data_len);
|
2005-04-15 17:48:20 +00:00
|
|
|
|
2020-04-01 14:05:45 +00:00
|
|
|
if (wrote < data_len) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
2019-10-17 10:17:09 +00:00
|
|
|
fclose(thefile);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto cleanup_and_exit;
|
2005-11-04 05:44:01 +00:00
|
|
|
}
|
2001-09-22 04:20:25 +00:00
|
|
|
|
2020-04-01 13:54:08 +00:00
|
|
|
/* If we've reached the last line of the buffer, don't write a newline
|
|
|
|
* character after it. If this last line is empty, it means zero bytes
|
|
|
|
* are written for it, and we don't count it in the number of lines. */
|
|
|
|
if (line->next == NULL) {
|
2020-04-01 14:05:45 +00:00
|
|
|
if (line->data[0] != '\0')
|
|
|
|
lineswritten++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-04-01 14:05:45 +00:00
|
|
|
if (openfile->fmt == DOS_FILE || openfile->fmt == MAC_FILE) {
|
|
|
|
if (putc('\r', thefile) == EOF) {
|
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
|
|
|
fclose(thefile);
|
|
|
|
goto cleanup_and_exit;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-04-01 14:05:45 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-04-01 14:05:45 +00:00
|
|
|
if (openfile->fmt != MAC_FILE)
|
2001-09-21 02:37:01 +00:00
|
|
|
#endif
|
2020-04-01 14:05:45 +00:00
|
|
|
if (putc('\n', thefile) == EOF) {
|
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
|
|
|
fclose(thefile);
|
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-10-16 09:04:26 +00:00
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
lineswritten++;
|
2005-11-04 05:44:01 +00:00
|
|
|
}
|
2000-06-19 04:22:15 +00:00
|
|
|
|
2018-07-24 17:31:03 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-16 15:16:48 +00:00
|
|
|
/* When prepending, append the temporary file to what we wrote above. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (method == PREPEND) {
|
2019-10-17 10:08:50 +00:00
|
|
|
FILE *source = fopen(tempname, "rb");
|
2019-10-18 10:31:31 +00:00
|
|
|
int verdict;
|
2005-05-29 02:22:55 +00:00
|
|
|
|
2019-10-16 15:16:48 +00:00
|
|
|
if (source == NULL) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error reading temp file: %s"), strerror(errno));
|
2019-10-17 10:17:09 +00:00
|
|
|
fclose(thefile);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2005-05-29 02:22:55 +00:00
|
|
|
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
verdict = copy_file(source, thefile, FALSE);
|
2019-10-18 10:31:31 +00:00
|
|
|
|
2019-10-18 11:25:38 +00:00
|
|
|
if (verdict < 0) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error reading temp file: %s"), strerror(errno));
|
2020-07-14 11:04:34 +00:00
|
|
|
fclose(thefile);
|
2019-10-18 10:31:31 +00:00
|
|
|
goto cleanup_and_exit;
|
2019-10-18 11:25:38 +00:00
|
|
|
} else if (verdict > 0) {
|
2019-10-18 12:04:15 +00:00
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
2020-07-14 11:04:34 +00:00
|
|
|
fclose(thefile);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2002-04-16 03:15:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
unlink(tempname);
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
}
|
2018-07-24 17:31:03 +00:00
|
|
|
#endif
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
|
2020-07-17 16:33:10 +00:00
|
|
|
/* Ensure the data has reached the disk before reporting it as written. */
|
|
|
|
if (fflush(thefile) != 0 || fsync(fileno(thefile)) != 0) {
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
2020-07-17 16:33:10 +00:00
|
|
|
fclose(thefile);
|
files: improve the backup procedure to ensure no data is lost
The file-saving procedure that nano followed was not crash-safe
under ext4 (and perhaps other filesystems) when editing existing
files. Specifically, modifying an existing file could lead to data
loss, since a modified file is not replaced atomically on disk.
Using "-B" did not ensure crash-safety either since the backup might
not have persisted on disk when the writeout of the file started.
In addition, if I/O pressure filled up the remaining disk space
after an existing file is truncated during save, nano would not
be able to finish saving the file, which would remain truncated.
This change addresses these issues by making nano do the following:
1) Make a temporary backup of the file being written so that there
is no time window such that (a) an existing file is truncated, and
(b) the buffer corresponding to said file has not been flushed to
disk. Such time windows are dangerous because, under certain
circumstances, they can lead to data loss on some filesystems.
The backup is made by copying the original file, and a second
attempt to HOME is made in case that first copy fails.
2) Use fsync() so that, when the save finishes, it is certain
that the file has been successfully written to disk.
This addresses https://savannah.gnu.org/bugs/?58642.
Signed-off-by: Michalis Kokologiannakis <michalis@mpi-sws.org>
2020-06-29 10:43:41 +00:00
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
|
|
|
|
2019-10-18 12:16:55 +00:00
|
|
|
if (fclose(thefile) != 0) {
|
|
|
|
statusline(ALERT, _("Error writing %s: %s"), realname, strerror(errno));
|
|
|
|
goto cleanup_and_exit;
|
|
|
|
}
|
2015-08-11 17:43:08 +00:00
|
|
|
|
2018-05-31 14:27:13 +00:00
|
|
|
/* When having written an entire buffer, update some administrivia. */
|
|
|
|
if (fullbuffer && method == OVERWRITE && !tmp) {
|
2020-01-13 18:10:23 +00:00
|
|
|
/* If the filename was changed, write a new lockfile when needed,
|
|
|
|
* and check whether it means a different syntax gets used. */
|
2018-05-31 14:27:13 +00:00
|
|
|
if (strcmp(openfile->filename, realname) != 0) {
|
2020-01-13 18:10:23 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if (openfile->lock_filename != NULL) {
|
|
|
|
delete_lockfile(openfile->lock_filename);
|
|
|
|
free(openfile->lock_filename);
|
2020-02-10 08:40:12 +00:00
|
|
|
openfile->lock_filename = do_lockfile(realname, FALSE);
|
2020-01-13 18:10:23 +00:00
|
|
|
}
|
|
|
|
#endif
|
2020-04-28 14:32:04 +00:00
|
|
|
openfile->filename = mallocstrcpy(openfile->filename, realname);
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *oldname, *newname;
|
2017-08-15 15:18:34 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
oldname = openfile->syntax ? openfile->syntax->name : "";
|
2020-04-28 14:32:04 +00:00
|
|
|
find_and_prime_applicable_syntax();
|
2017-12-29 18:27:33 +00:00
|
|
|
newname = openfile->syntax ? openfile->syntax->name : "";
|
|
|
|
|
|
|
|
/* If the syntax changed, discard and recompute the multidata. */
|
|
|
|
if (strcmp(oldname, newname) != 0) {
|
2020-05-01 09:40:18 +00:00
|
|
|
for (line = openfile->filetop; line != NULL; line = line->next) {
|
|
|
|
free(line->multidata);
|
|
|
|
line->multidata = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2018-05-31 14:27:13 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
precalc_multicolorinfo();
|
2020-04-28 13:28:26 +00:00
|
|
|
have_palette = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2003-01-13 01:35:15 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-05-31 14:27:13 +00:00
|
|
|
/* Get or update the stat info to reflect the current state. */
|
2020-05-28 12:31:15 +00:00
|
|
|
stat_with_alloc(realname, &openfile->statinfo);
|
2017-12-18 19:08:06 +00:00
|
|
|
|
2018-05-31 14:27:13 +00:00
|
|
|
/* Record at which point in the undo stack the file was saved. */
|
|
|
|
openfile->last_saved = openfile->current_undo;
|
|
|
|
openfile->last_action = OTHER;
|
2002-06-28 22:45:14 +00:00
|
|
|
#endif
|
2018-05-31 14:27:13 +00:00
|
|
|
openfile->modified = FALSE;
|
|
|
|
titlebar(NULL);
|
2018-05-31 14:03:05 +00:00
|
|
|
}
|
2005-07-15 19:37:32 +00:00
|
|
|
|
2018-05-31 14:03:05 +00:00
|
|
|
if (!tmp)
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(HUSH, P_("Wrote %zu line", "Wrote %zu lines",
|
2018-05-31 14:27:13 +00:00
|
|
|
lineswritten), lineswritten);
|
2017-12-29 18:27:33 +00:00
|
|
|
retval = TRUE;
|
2002-09-13 18:14:04 +00:00
|
|
|
|
|
|
|
cleanup_and_exit:
|
2017-12-29 18:27:33 +00:00
|
|
|
free(tempname);
|
2019-10-18 12:16:55 +00:00
|
|
|
free(realname);
|
2005-05-29 02:22:55 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return retval;
|
2000-06-19 04:22:15 +00:00
|
|
|
}
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-07-28 16:51:15 +00:00
|
|
|
/* Write the marked region of the current buffer out to disk.
|
|
|
|
* Return TRUE on success and FALSE on error. */
|
2019-10-16 11:07:41 +00:00
|
|
|
bool write_marked_file(const char *name, FILE *stream, bool tmp,
|
2017-12-29 18:27:33 +00:00
|
|
|
kind_of_writing_type method)
|
2004-01-23 19:34:03 +00:00
|
|
|
{
|
2020-04-01 17:56:39 +00:00
|
|
|
linestruct *birthline, *topline, *botline, *stopper, *afterline;
|
|
|
|
char *was_datastart, saved_byte;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t top_x, bot_x;
|
2020-04-01 17:56:39 +00:00
|
|
|
bool retval;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-04-01 17:56:39 +00:00
|
|
|
get_region(&topline, &top_x, &botline, &bot_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-04-01 17:56:39 +00:00
|
|
|
/* When needed, prepare a magic end line for the region. */
|
|
|
|
if (bot_x > 0 && !ISSET(NO_NEWLINES)) {
|
|
|
|
stopper = make_new_node(botline);
|
|
|
|
stopper->data = copy_of("");
|
|
|
|
} else
|
|
|
|
stopper = NULL;
|
|
|
|
|
|
|
|
/* Make the marked area look like a separate buffer. */
|
|
|
|
afterline = botline->next;
|
|
|
|
botline->next = stopper;
|
|
|
|
saved_byte = botline->data[bot_x];
|
|
|
|
botline->data[bot_x] = '\0';
|
|
|
|
was_datastart = topline->data;
|
|
|
|
topline->data += top_x;
|
|
|
|
birthline = openfile->filetop;
|
|
|
|
openfile->filetop = topline;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-10-16 11:07:41 +00:00
|
|
|
retval = write_file(name, stream, tmp, method, FALSE);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-04-01 17:56:39 +00:00
|
|
|
/* Restore the proper state of the buffer. */
|
|
|
|
openfile->filetop = birthline;
|
|
|
|
topline->data = was_datastart;
|
|
|
|
botline->data[bot_x] = saved_byte;
|
|
|
|
botline->next = afterline;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-04-01 17:56:39 +00:00
|
|
|
if (stopper)
|
|
|
|
delete_node(stopper);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
return retval;
|
2004-01-23 19:34:03 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2004-01-23 19:34:03 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Write the current file to disk. If the mark is on, write the current
|
2015-12-23 16:34:44 +00:00
|
|
|
* marked selection to disk. If exiting is TRUE, write the entire file
|
2017-10-14 09:55:09 +00:00
|
|
|
* to disk regardless of whether the mark is on. Do not ask for a name
|
2020-04-30 15:25:48 +00:00
|
|
|
* when withprompt is FALSE nor when the SAVE_ON_EXIT flag is set and the
|
2017-10-14 09:55:09 +00:00
|
|
|
* file already has a name. Return 0 on error, 1 on success, and 2 when
|
|
|
|
* the buffer is to be discarded. */
|
2017-09-27 20:42:48 +00:00
|
|
|
int do_writeout(bool exiting, bool withprompt)
|
2000-06-19 04:22:15 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
bool result = FALSE;
|
|
|
|
kind_of_writing_type method = OVERWRITE;
|
|
|
|
char *given;
|
|
|
|
/* The filename we offer, or what the user typed so far. */
|
|
|
|
bool maychange = (openfile->filename[0] == '\0');
|
|
|
|
/* Whether it's okay to save the file under a different name. */
|
2017-11-01 19:33:14 +00:00
|
|
|
#ifdef ENABLE_EXTRA
|
2017-12-29 18:27:33 +00:00
|
|
|
static bool did_credits = FALSE;
|
2000-11-24 20:45:14 +00:00
|
|
|
#endif
|
2000-06-19 04:22:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Display newlines in filenames as ^J. */
|
|
|
|
as_an_at = FALSE;
|
2016-12-20 18:27:41 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-13 14:11:15 +00:00
|
|
|
given = copy_of((openfile->mark && !exiting) ? "" : openfile->filename);
|
|
|
|
#else
|
|
|
|
given = copy_of(openfile->filename);
|
2004-03-15 20:26:30 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
2019-03-28 21:28:47 +00:00
|
|
|
int response = 0, choice = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
functionptrtype func;
|
2020-06-21 07:18:24 +00:00
|
|
|
const char *msg;
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *formatstr, *backupstr;
|
|
|
|
|
|
|
|
formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
|
|
|
|
(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
|
2020-05-25 16:52:09 +00:00
|
|
|
backupstr = ISSET(MAKE_BACKUP) ? _(" [Backup]") : "";
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* When the mark is on, offer to write the selection to disk, but
|
|
|
|
* not when in restricted mode, because it would allow writing to
|
|
|
|
* a file not specified on the command line. */
|
|
|
|
if (openfile->mark && !exiting && !ISSET(RESTRICTED))
|
|
|
|
/* TRANSLATORS: The next six strings are prompts. */
|
|
|
|
msg = (method == PREPEND) ? _("Prepend Selection to File") :
|
|
|
|
(method == APPEND) ? _("Append Selection to File") :
|
|
|
|
_("Write Selection to File");
|
|
|
|
else if (method != OVERWRITE)
|
|
|
|
msg = (method == PREPEND) ? _("File Name to Prepend to") :
|
|
|
|
_("File Name to Append to");
|
|
|
|
else
|
2020-07-28 16:51:15 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
msg = _("File Name to Write");
|
|
|
|
|
|
|
|
present_path = mallocstrcpy(present_path, "./");
|
|
|
|
|
2020-06-21 07:18:24 +00:00
|
|
|
/* When we shouldn't prompt, use the existing filename.
|
|
|
|
* Otherwise, ask for (confirmation of) the filename. */
|
2020-04-30 15:25:48 +00:00
|
|
|
if ((!withprompt || (ISSET(SAVE_ON_EXIT) && exiting)) &&
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->filename[0] != '\0')
|
|
|
|
answer = mallocstrcpy(answer, openfile->filename);
|
2020-06-21 07:18:24 +00:00
|
|
|
else
|
2020-06-20 15:15:40 +00:00
|
|
|
response = do_prompt(MWRITEFILE, given, NULL,
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh, "%s%s%s", msg,
|
2006-02-18 21:32:29 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
formatstr, backupstr
|
2004-03-15 20:26:30 +00:00
|
|
|
#else
|
2017-12-29 18:27:33 +00:00
|
|
|
"", ""
|
2004-03-15 20:26:30 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
);
|
2004-03-15 20:26:30 +00:00
|
|
|
|
2019-02-20 18:24:18 +00:00
|
|
|
if (response < 0) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Cancelled"));
|
|
|
|
break;
|
|
|
|
}
|
2017-11-22 18:26:47 +00:00
|
|
|
|
2019-02-20 18:24:18 +00:00
|
|
|
func = func_from_key(&response);
|
2014-07-27 21:07:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Upon request, abandon the buffer. */
|
|
|
|
if (func == discard_buffer) {
|
|
|
|
free(given);
|
|
|
|
return 2;
|
|
|
|
}
|
2015-12-23 16:34:44 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
given = mallocstrcpy(given, answer);
|
2000-06-19 04:22:15 +00:00
|
|
|
|
2017-05-08 17:08:23 +00:00
|
|
|
#ifdef ENABLE_BROWSER
|
2020-01-26 15:36:23 +00:00
|
|
|
if (func == to_files) {
|
2020-07-04 15:43:16 +00:00
|
|
|
char *chosen = browse_in(answer);
|
2001-05-21 12:56:25 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (chosen == NULL)
|
|
|
|
continue;
|
2005-07-08 20:09:16 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(answer);
|
|
|
|
answer = chosen;
|
|
|
|
} else
|
2017-05-08 17:08:23 +00:00
|
|
|
#endif
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (func == dos_format_void) {
|
|
|
|
openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
|
|
|
|
continue;
|
|
|
|
} else if (func == mac_format_void) {
|
|
|
|
openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
|
|
|
|
continue;
|
|
|
|
} else if (func == backup_file_void) {
|
2020-05-25 16:52:09 +00:00
|
|
|
TOGGLE(MAKE_BACKUP);
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
} else if (func == prepend_void) {
|
|
|
|
method = (method == PREPEND) ? OVERWRITE : PREPEND;
|
|
|
|
continue;
|
|
|
|
} else if (func == append_void) {
|
|
|
|
method = (method == APPEND) ? OVERWRITE : APPEND;
|
|
|
|
continue;
|
|
|
|
}
|
2020-07-28 16:51:15 +00:00
|
|
|
#endif
|
2019-12-15 18:47:05 +00:00
|
|
|
if (func == do_help) {
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-05-07 11:34:07 +00:00
|
|
|
|
2017-11-01 19:33:14 +00:00
|
|
|
#ifdef ENABLE_EXTRA
|
2020-05-07 11:34:07 +00:00
|
|
|
/* If the user pressed Ctrl-X in the edit window, and answered "Y" at
|
|
|
|
* the "Save modified buffer?" prompt, and entered "zzy" as filename,
|
|
|
|
* and this is the first time around, show an Easter egg. */
|
|
|
|
if (exiting && !ISSET(SAVE_ON_EXIT) && openfile->filename[0] == '\0' &&
|
|
|
|
strcmp(answer, "zzy") == 0 && !did_credits) {
|
2018-07-12 18:44:36 +00:00
|
|
|
if (LINES > 5 && COLS > 31) {
|
|
|
|
do_credits();
|
|
|
|
did_credits = TRUE;
|
|
|
|
} else
|
2018-08-29 18:05:37 +00:00
|
|
|
/* TRANSLATORS: Concisely say the screen is too small. */
|
2018-07-14 10:07:25 +00:00
|
|
|
statusbar(_("Too tiny"));
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2017-11-22 18:43:50 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
#endif
|
2006-12-15 02:49:44 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (method == OVERWRITE) {
|
|
|
|
bool name_exists, do_warning;
|
|
|
|
char *full_answer, *full_filename;
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
full_answer = get_full_path(answer);
|
|
|
|
full_filename = get_full_path(openfile->filename);
|
|
|
|
name_exists = (stat((full_answer == NULL) ?
|
|
|
|
answer : full_answer, &st) != -1);
|
|
|
|
if (openfile->filename[0] == '\0')
|
|
|
|
do_warning = name_exists;
|
|
|
|
else
|
|
|
|
do_warning = (strcmp((full_answer == NULL) ?
|
|
|
|
answer : full_answer, (full_filename == NULL) ?
|
|
|
|
openfile->filename : full_filename) != 0);
|
|
|
|
|
|
|
|
free(full_filename);
|
|
|
|
free(full_answer);
|
|
|
|
|
|
|
|
if (do_warning) {
|
|
|
|
/* When in restricted mode, we aren't allowed to overwrite
|
|
|
|
* an existing file with the current buffer, nor to change
|
|
|
|
* the name of the current file if it already has one. */
|
|
|
|
if (ISSET(RESTRICTED)) {
|
|
|
|
/* TRANSLATORS: Restricted mode forbids overwriting. */
|
2020-05-28 14:59:50 +00:00
|
|
|
warn_and_briefly_pause(_("File exists -- "
|
2017-12-29 18:27:33 +00:00
|
|
|
"cannot overwrite"));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!maychange) {
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (exiting || !openfile->mark)
|
2016-03-20 14:34:46 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
{
|
|
|
|
if (do_yesno_prompt(FALSE, _("Save file under "
|
|
|
|
"DIFFERENT NAME? ")) < 1)
|
|
|
|
continue;
|
|
|
|
maychange = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name_exists) {
|
|
|
|
char *question = _("File \"%s\" exists; OVERWRITE? ");
|
2018-01-25 10:00:19 +00:00
|
|
|
char *name = display_string(answer, 0,
|
2019-04-24 06:49:18 +00:00
|
|
|
COLS - breadth(question) + 1, FALSE, FALSE);
|
2017-12-29 18:27:33 +00:00
|
|
|
char *message = charalloc(strlen(question) +
|
2018-01-25 10:00:19 +00:00
|
|
|
strlen(name) + 1);
|
|
|
|
|
|
|
|
sprintf(message, question, name);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-02-10 14:40:08 +00:00
|
|
|
choice = do_yesno_prompt(FALSE, message);
|
2018-01-25 10:00:19 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(message);
|
2018-01-25 10:00:19 +00:00
|
|
|
free(name);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-02-10 14:40:08 +00:00
|
|
|
if (choice < 1)
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2008-10-22 22:11:46 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Complain if the file exists, the name hasn't changed,
|
|
|
|
* and the stat information we had before does not match
|
|
|
|
* what we have now. */
|
2020-05-28 12:31:15 +00:00
|
|
|
else if (name_exists && openfile->statinfo &&
|
|
|
|
(openfile->statinfo->st_mtime < st.st_mtime ||
|
|
|
|
openfile->statinfo->st_dev != st.st_dev ||
|
|
|
|
openfile->statinfo->st_ino != st.st_ino)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-05-28 14:59:50 +00:00
|
|
|
warn_and_briefly_pause(_("File on disk has changed"));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-07-27 08:33:42 +00:00
|
|
|
/* TRANSLATORS: Try to keep this at most 76 characters. */
|
2019-02-10 15:11:57 +00:00
|
|
|
choice = do_yesno_prompt(FALSE, _("File was modified "
|
2017-12-29 18:27:33 +00:00
|
|
|
"since you opened it; continue saving? "));
|
|
|
|
wipe_statusbar();
|
|
|
|
|
|
|
|
/* When in tool mode and not called by 'savefile',
|
|
|
|
* overwrite the file right here when requested. */
|
2020-04-30 15:25:48 +00:00
|
|
|
if (ISSET(SAVE_ON_EXIT) && withprompt) {
|
2017-12-29 18:27:33 +00:00
|
|
|
free(given);
|
2019-02-10 15:11:57 +00:00
|
|
|
if (choice == 1)
|
2017-12-29 20:35:14 +00:00
|
|
|
return write_file(openfile->filename, NULL,
|
|
|
|
FALSE, OVERWRITE, TRUE);
|
2019-02-10 15:11:57 +00:00
|
|
|
else if (choice == 0)
|
2017-12-29 18:27:33 +00:00
|
|
|
return 2;
|
|
|
|
else
|
|
|
|
return 0;
|
2019-02-10 15:11:57 +00:00
|
|
|
} else if (choice != 1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
free(given);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2017-11-22 18:43:50 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2000-06-19 04:22:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Here's where we allow the selected text to be written to
|
|
|
|
* a separate file. If we're using restricted mode, this
|
|
|
|
* function is disabled, since it allows reading from or
|
|
|
|
* writing to files not specified on the command line. */
|
2007-01-11 23:10:03 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->mark && !exiting && withprompt && !ISSET(RESTRICTED))
|
|
|
|
result = write_marked_file(answer, NULL, FALSE, method);
|
|
|
|
else
|
2006-11-07 21:08:17 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
result = write_file(answer, NULL, FALSE, method, TRUE);
|
2001-07-11 02:08:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2004-10-05 20:11:31 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(given);
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return result ? 1 : 0;
|
2000-06-19 04:22:15 +00:00
|
|
|
}
|
|
|
|
|
2015-12-23 16:34:44 +00:00
|
|
|
/* Write the current buffer to disk, or discard it. */
|
2004-07-02 14:31:03 +00:00
|
|
|
void do_writeout_void(void)
|
2000-06-19 04:22:15 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the user chose to discard the buffer, close it. */
|
|
|
|
if (do_writeout(FALSE, TRUE) == 2)
|
|
|
|
close_and_go();
|
2000-06-19 04:22:15 +00:00
|
|
|
}
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2015-07-25 19:25:50 +00:00
|
|
|
/* If it has a name, write the current file to disk without prompting. */
|
|
|
|
void do_savefile(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
if (do_writeout(FALSE, FALSE) == 2)
|
|
|
|
close_and_go();
|
2015-07-25 19:25:50 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 12:08:38 +00:00
|
|
|
/* Convert the tilde notation when the given path begins with ~/ or ~user/.
|
|
|
|
* Return an allocated string containing the expanded path. */
|
2019-09-17 12:18:03 +00:00
|
|
|
char *real_dir_from_tilde(const char *path)
|
2000-11-24 14:00:16 +00:00
|
|
|
{
|
2019-09-17 12:18:03 +00:00
|
|
|
char *tilded, *retval;
|
2019-09-17 12:11:45 +00:00
|
|
|
size_t i = 1;
|
2001-01-23 03:09:15 +00:00
|
|
|
|
2019-09-17 12:18:03 +00:00
|
|
|
if (*path != '~')
|
2019-10-13 10:24:27 +00:00
|
|
|
return copy_of(path);
|
2019-09-17 12:08:38 +00:00
|
|
|
|
2019-09-17 12:11:45 +00:00
|
|
|
/* Figure out how much of the string we need to compare. */
|
2019-09-17 12:18:03 +00:00
|
|
|
while (path[i] != '/' && path[i] != '\0')
|
2019-09-17 12:11:45 +00:00
|
|
|
i++;
|
2002-09-13 18:14:04 +00:00
|
|
|
|
2019-09-17 12:11:45 +00:00
|
|
|
if (i == 1) {
|
|
|
|
get_homedir();
|
2019-10-13 10:24:27 +00:00
|
|
|
tilded = copy_of(homedir);
|
2019-09-17 12:11:45 +00:00
|
|
|
} else {
|
2017-02-21 22:04:44 +00:00
|
|
|
#ifdef HAVE_PWD_H
|
2019-09-17 12:11:45 +00:00
|
|
|
const struct passwd *userdata;
|
|
|
|
|
2020-02-20 15:52:48 +00:00
|
|
|
tilded = measured_copy(path, i);
|
2019-09-17 12:11:45 +00:00
|
|
|
|
|
|
|
do {
|
|
|
|
userdata = getpwent();
|
2019-09-17 12:18:03 +00:00
|
|
|
} while (userdata && strcmp(userdata->pw_name, tilded + 1) != 0);
|
2019-09-17 12:11:45 +00:00
|
|
|
endpwent();
|
2019-09-17 12:18:03 +00:00
|
|
|
|
2019-09-17 12:11:45 +00:00
|
|
|
if (userdata != NULL)
|
2019-09-17 12:18:03 +00:00
|
|
|
tilded = mallocstrcpy(tilded, userdata->pw_dir);
|
2017-02-21 22:04:44 +00:00
|
|
|
#else
|
2019-10-13 10:38:46 +00:00
|
|
|
tilded = copy_of("");
|
2017-02-21 22:04:44 +00:00
|
|
|
#endif
|
2019-09-17 12:11:45 +00:00
|
|
|
}
|
2001-01-23 03:09:15 +00:00
|
|
|
|
2019-09-17 12:18:03 +00:00
|
|
|
retval = charalloc(strlen(tilded) + strlen(path + i) + 1);
|
|
|
|
sprintf(retval, "%s%s", tilded, path + i);
|
2000-11-25 04:43:43 +00:00
|
|
|
|
2019-09-17 12:18:03 +00:00
|
|
|
free(tilded);
|
2002-09-13 18:14:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return retval;
|
2000-11-24 14:00:16 +00:00
|
|
|
}
|
|
|
|
|
2017-05-08 17:15:51 +00:00
|
|
|
#if defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER)
|
2005-02-11 16:02:54 +00:00
|
|
|
/* Our sort routine for file listings. Sort alphabetically and
|
2005-06-21 04:11:04 +00:00
|
|
|
* case-insensitively, and sort directories before filenames. */
|
2005-02-08 20:37:53 +00:00
|
|
|
int diralphasort(const void *va, const void *vb)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
struct stat fileinfo;
|
|
|
|
const char *a = *(const char *const *)va;
|
|
|
|
const char *b = *(const char *const *)vb;
|
|
|
|
bool aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
|
|
|
|
bool bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
|
|
|
|
|
|
|
|
if (aisdir && !bisdir)
|
|
|
|
return -1;
|
|
|
|
if (!aisdir && bisdir)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
return mbstrcasecmp(a, b);
|
2005-02-08 20:37:53 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-05-08 17:15:51 +00:00
|
|
|
#ifdef ENABLE_TABCOMP
|
2018-11-19 15:38:54 +00:00
|
|
|
/* Return TRUE when the given path is a directory. */
|
|
|
|
bool is_dir(const char *path)
|
2000-11-24 14:00:16 +00:00
|
|
|
{
|
2018-11-19 15:38:54 +00:00
|
|
|
char *realpath = real_dir_from_tilde(path);
|
2017-12-29 18:27:33 +00:00
|
|
|
struct stat fileinfo;
|
|
|
|
bool retval;
|
2007-01-11 21:36:29 +00:00
|
|
|
|
2018-11-19 15:38:54 +00:00
|
|
|
retval = (stat(realpath, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
|
2007-01-11 21:36:29 +00:00
|
|
|
|
2018-11-19 15:38:54 +00:00
|
|
|
free(realpath);
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return retval;
|
2000-11-24 14:00:16 +00:00
|
|
|
}
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
/* Try to complete the given fragment of given length to a username. */
|
|
|
|
char **username_completion(const char *morsel, size_t length, size_t *num_matches)
|
2000-11-05 17:54:41 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char **matches = NULL;
|
2017-07-01 11:25:11 +00:00
|
|
|
#ifdef HAVE_PWD_H
|
2017-12-29 18:27:33 +00:00
|
|
|
const struct passwd *userdata;
|
2001-01-19 04:17:24 +00:00
|
|
|
|
2020-06-18 15:10:05 +00:00
|
|
|
/* Iterate through the entries in the passwd file, and
|
|
|
|
* add each fitting username to the list of matches. */
|
2017-12-29 18:27:33 +00:00
|
|
|
while ((userdata = getpwent()) != NULL) {
|
2020-07-14 15:29:13 +00:00
|
|
|
if (strncmp(userdata->pw_name, morsel + 1, length - 1) == 0) {
|
2017-10-29 20:08:07 +00:00
|
|
|
#ifdef ENABLE_OPERATINGDIR
|
2020-06-18 15:10:05 +00:00
|
|
|
/* Skip directories that are outside of the allowed area. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (outside_of_confinement(userdata->pw_dir, TRUE))
|
|
|
|
continue;
|
2001-09-19 03:19:43 +00:00
|
|
|
#endif
|
2020-06-18 15:10:05 +00:00
|
|
|
matches = (char **)nrealloc(matches, (*num_matches + 1) * sizeof(char *));
|
2017-12-29 18:27:33 +00:00
|
|
|
matches[*num_matches] = charalloc(strlen(userdata->pw_name) + 2);
|
|
|
|
sprintf(matches[*num_matches], "~%s", userdata->pw_name);
|
|
|
|
++(*num_matches);
|
|
|
|
}
|
2000-11-24 14:00:16 +00:00
|
|
|
}
|
2020-06-18 09:44:25 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
endpwent();
|
2017-02-21 22:04:44 +00:00
|
|
|
#endif
|
2001-01-19 04:17:24 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return matches;
|
2000-11-05 17:54:41 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 15:10:05 +00:00
|
|
|
/* The next two functions were adapted from busybox 0.46 (cmdedit.c).
|
|
|
|
* Here is the tweaked notice from that file:
|
2020-06-18 10:31:22 +00:00
|
|
|
*
|
|
|
|
* Termios command-line History and Editing, originally intended for NetBSD.
|
|
|
|
* Copyright (C) 1999, 2000
|
|
|
|
* Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
|
|
|
|
* Etc: Dave Cinege <dcinege@psychosis.com>
|
|
|
|
* Adjusted/rewritten: Erik Andersen <andersee@debian.org>
|
|
|
|
*
|
|
|
|
* You may use this code as you wish, so long as the original author(s)
|
|
|
|
* are attributed in any redistributions of the source code.
|
|
|
|
* This code is 'as is' with no warranty.
|
|
|
|
* This code may safely be consumed by a BSD or GPL license. */
|
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
/* Try to complete the given fragment of given length to a filename. */
|
|
|
|
char **filename_completion(const char *morsel, size_t length, size_t *num_matches)
|
2000-11-05 17:54:41 +00:00
|
|
|
{
|
2020-07-14 15:29:13 +00:00
|
|
|
char *dirname = copy_of(morsel);
|
2017-12-29 18:27:33 +00:00
|
|
|
char *slash, *filename;
|
|
|
|
size_t filenamelen;
|
2020-06-26 07:53:55 +00:00
|
|
|
char *fullname = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
char **matches = NULL;
|
2020-06-19 08:02:40 +00:00
|
|
|
const struct dirent *entry;
|
2020-06-26 07:53:55 +00:00
|
|
|
DIR *dir;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If there's a / in the name, split out filename and directory parts. */
|
|
|
|
slash = strrchr(dirname, '/');
|
|
|
|
if (slash != NULL) {
|
|
|
|
char *wasdirname = dirname;
|
|
|
|
|
2019-10-13 10:24:27 +00:00
|
|
|
filename = copy_of(++slash);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Cut off the filename part after the slash. */
|
|
|
|
*slash = '\0';
|
|
|
|
dirname = real_dir_from_tilde(dirname);
|
|
|
|
/* A non-absolute path is relative to the current browser directory. */
|
|
|
|
if (dirname[0] != '/') {
|
2020-06-18 15:24:18 +00:00
|
|
|
dirname = charealloc(dirname, strlen(present_path) + strlen(wasdirname) + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
sprintf(dirname, "%s%s", present_path, wasdirname);
|
|
|
|
}
|
|
|
|
free(wasdirname);
|
|
|
|
} else {
|
|
|
|
filename = dirname;
|
2019-10-13 10:24:27 +00:00
|
|
|
dirname = copy_of(present_path);
|
2016-04-30 19:22:16 +00:00
|
|
|
}
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
dir = opendir(dirname);
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dir == NULL) {
|
|
|
|
beep();
|
|
|
|
free(filename);
|
|
|
|
free(dirname);
|
|
|
|
return NULL;
|
|
|
|
}
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
filenamelen = strlen(filename);
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2020-06-18 09:44:25 +00:00
|
|
|
/* Iterate through the filenames in the directory,
|
2020-06-18 15:10:05 +00:00
|
|
|
* and add each fitting one to the list of matches. */
|
2020-06-19 08:02:40 +00:00
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
|
|
if (strncmp(entry->d_name, filename, filenamelen) == 0 &&
|
|
|
|
strcmp(entry->d_name, ".") != 0 &&
|
|
|
|
strcmp(entry->d_name, "..") != 0) {
|
2020-06-26 07:53:55 +00:00
|
|
|
fullname = charealloc(fullname, strlen(dirname) +
|
|
|
|
strlen(entry->d_name) + 1);
|
2020-06-18 09:44:25 +00:00
|
|
|
|
2020-06-19 08:02:40 +00:00
|
|
|
sprintf(fullname, "%s%s", dirname, entry->d_name);
|
2006-02-07 21:11:05 +00:00
|
|
|
|
2017-10-29 20:08:07 +00:00
|
|
|
#ifdef ENABLE_OPERATINGDIR
|
2020-06-26 07:53:55 +00:00
|
|
|
if (outside_of_confinement(fullname, TRUE))
|
2020-06-18 17:02:02 +00:00
|
|
|
continue;
|
2006-02-07 21:11:05 +00:00
|
|
|
#endif
|
2020-06-26 07:53:55 +00:00
|
|
|
if (currmenu == MGOTODIR && !is_dir(fullname))
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
2020-06-26 07:47:38 +00:00
|
|
|
|
2020-06-18 15:10:05 +00:00
|
|
|
matches = (char **)nrealloc(matches, (*num_matches + 1) * sizeof(char *));
|
2020-06-19 08:02:40 +00:00
|
|
|
matches[*num_matches] = copy_of(entry->d_name);
|
2017-12-29 18:27:33 +00:00
|
|
|
++(*num_matches);
|
|
|
|
}
|
2000-11-05 17:54:41 +00:00
|
|
|
}
|
2005-07-08 20:09:16 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
closedir(dir);
|
|
|
|
free(dirname);
|
|
|
|
free(filename);
|
2020-06-26 07:53:55 +00:00
|
|
|
free(fullname);
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return matches;
|
2000-11-05 17:54:41 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 15:10:05 +00:00
|
|
|
/* Do tab completion. 'place' is the position of the status-bar cursor, and
|
|
|
|
* 'refresh_func' is the function to be called to refresh the edit window. */
|
2020-07-14 15:29:13 +00:00
|
|
|
char *input_tab(char *morsel, size_t *place, void (*refresh_func)(void), bool *listed)
|
2000-11-05 17:54:41 +00:00
|
|
|
{
|
2020-06-18 09:54:40 +00:00
|
|
|
size_t num_matches = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
char **matches = NULL;
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2020-06-18 09:54:40 +00:00
|
|
|
/* If the cursor is not at the end of the fragment, do nothing. */
|
2020-07-14 15:29:13 +00:00
|
|
|
if (morsel[*place] != '\0') {
|
2020-06-18 09:54:40 +00:00
|
|
|
beep();
|
2020-07-14 15:29:13 +00:00
|
|
|
return morsel;
|
2020-06-18 09:54:40 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 15:10:05 +00:00
|
|
|
/* If the fragment starts with a tilde and contains no slash,
|
|
|
|
* then try completing it as a username. */
|
2020-07-14 15:29:13 +00:00
|
|
|
if (morsel[0] == '~' && strchr(morsel, '/') == NULL)
|
|
|
|
matches = username_completion(morsel, *place, &num_matches);
|
2000-11-14 17:46:06 +00:00
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
/* If there are no matches yet, try matching against filenames. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (matches == NULL)
|
2020-07-14 15:29:13 +00:00
|
|
|
matches = filename_completion(morsel, *place, &num_matches);
|
2000-11-14 17:46:06 +00:00
|
|
|
|
2020-06-21 08:02:45 +00:00
|
|
|
/* If possible completions were listed before but none will be listed now... */
|
|
|
|
if (*listed && num_matches < 2) {
|
|
|
|
refresh_func();
|
|
|
|
*listed = FALSE;
|
|
|
|
}
|
|
|
|
|
2020-06-21 08:13:02 +00:00
|
|
|
if (matches == NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
beep();
|
2020-07-14 15:29:13 +00:00
|
|
|
return morsel;
|
2020-06-21 08:13:02 +00:00
|
|
|
}
|
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
const char *lastslash = revstrstr(morsel, "/", morsel + *place);
|
|
|
|
size_t length_of_path = (lastslash == NULL) ? 0 : lastslash - morsel + 1;
|
2020-06-21 08:17:55 +00:00
|
|
|
size_t match, common_len = 0;
|
2020-07-14 15:29:13 +00:00
|
|
|
char *shared, *glued;
|
2020-06-21 08:17:55 +00:00
|
|
|
char char1[MAXCHARLEN], char2[MAXCHARLEN];
|
|
|
|
int len1, len2;
|
|
|
|
|
|
|
|
/* Determine the number of characters that all matches have in common. */
|
|
|
|
while (TRUE) {
|
|
|
|
len1 = collect_char(matches[0] + common_len, char1);
|
2000-11-25 04:43:43 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
for (match = 1; match < num_matches; match++) {
|
|
|
|
len2 = collect_char(matches[match] + common_len, char2);
|
2000-11-14 17:46:06 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
if (len1 != len2 || strncmp(char1, char2, len2) != 0)
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2000-11-14 17:46:06 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
if (match < num_matches || matches[0][common_len] == '\0')
|
|
|
|
break;
|
2006-10-03 18:46:00 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
common_len += len1;
|
|
|
|
}
|
2000-11-14 17:46:06 +00:00
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
shared = charalloc(length_of_path + common_len + 1);
|
2001-09-19 03:19:43 +00:00
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
strncpy(shared, morsel, length_of_path);
|
|
|
|
strncpy(shared + length_of_path, matches[0], common_len);
|
2016-04-30 19:22:16 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
common_len += length_of_path;
|
2020-07-14 15:29:13 +00:00
|
|
|
shared[common_len] = '\0';
|
2005-05-26 03:32:41 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* Cover also the case of the user specifying a relative path. */
|
2020-07-14 15:22:08 +00:00
|
|
|
glued = charalloc(strlen(present_path) + common_len + 1);
|
2020-07-14 15:29:13 +00:00
|
|
|
sprintf(glued, "%s%s", present_path, shared);
|
2016-02-21 13:33:52 +00:00
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
if (num_matches == 1 && (is_dir(shared) || is_dir(glued)))
|
|
|
|
shared[common_len++] = '/';
|
2000-11-05 21:54:23 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* If the matches have something in common, copy that part. */
|
|
|
|
if (common_len != *place) {
|
2020-07-14 15:29:13 +00:00
|
|
|
morsel = charealloc(morsel, common_len + 1);
|
2020-07-25 11:19:19 +00:00
|
|
|
strncpy(morsel, shared, common_len);
|
|
|
|
morsel[common_len] = '\0';
|
2020-06-21 08:17:55 +00:00
|
|
|
*place = common_len;
|
2020-06-25 08:10:49 +00:00
|
|
|
} else if (num_matches == 1)
|
|
|
|
beep();
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* If there is more than one possible completion, show a sorted list. */
|
|
|
|
if (num_matches > 1) {
|
2020-06-21 08:44:05 +00:00
|
|
|
size_t longest_name = 0;
|
|
|
|
size_t nrows, ncols;
|
|
|
|
int row;
|
2005-05-26 18:20:05 +00:00
|
|
|
|
2020-06-25 08:10:49 +00:00
|
|
|
if (!*listed)
|
|
|
|
beep();
|
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
qsort(matches, num_matches, sizeof(char *), diralphasort);
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* Find the length of the longest name among the matches. */
|
|
|
|
for (match = 0; match < num_matches; match++) {
|
|
|
|
size_t namelen = breadth(matches[match]);
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
if (namelen > longest_name)
|
|
|
|
longest_name = namelen;
|
|
|
|
}
|
2000-11-15 01:25:42 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
if (longest_name > COLS - 1)
|
|
|
|
longest_name = COLS - 1;
|
2000-11-05 21:56:54 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* The columns of names will be separated by two spaces,
|
|
|
|
* but the last column will have just one space after it. */
|
|
|
|
ncols = (COLS + 1) / (longest_name + 2);
|
2020-06-21 08:44:05 +00:00
|
|
|
nrows = (num_matches + ncols - 1) / ncols;
|
|
|
|
|
|
|
|
row = (nrows < editwinrows - 1) ? editwinrows - nrows - 1 : 0;
|
2000-11-14 18:25:26 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* Blank the edit window and hide the cursor. */
|
|
|
|
blank_edit();
|
|
|
|
curs_set(0);
|
2000-11-05 21:54:23 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
/* Now print the list of matches out there. */
|
|
|
|
for (match = 0; match < num_matches; match++) {
|
|
|
|
char *disp;
|
2000-11-05 21:54:23 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
wmove(edit, row, (longest_name + 2) * (match % ncols));
|
2000-11-05 22:48:35 +00:00
|
|
|
|
2020-06-21 09:00:57 +00:00
|
|
|
if (row == editwinrows - 1 && (match + 1) % ncols == 0 &&
|
|
|
|
match + 1 < num_matches) {
|
2020-06-21 08:17:55 +00:00
|
|
|
waddstr(edit, _("(more)"));
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-01 22:58:47 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
disp = display_string(matches[match], 0, longest_name, FALSE, FALSE);
|
|
|
|
waddstr(edit, disp);
|
|
|
|
free(disp);
|
|
|
|
|
|
|
|
if ((match + 1) % ncols == 0)
|
|
|
|
row++;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2020-06-21 08:17:55 +00:00
|
|
|
wnoutrefresh(edit);
|
|
|
|
*listed = TRUE;
|
|
|
|
}
|
2000-11-05 17:54:41 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free_chararray(matches, num_matches);
|
2020-06-21 08:17:55 +00:00
|
|
|
free(glued);
|
2020-07-14 15:29:13 +00:00
|
|
|
free(shared);
|
2005-02-08 20:37:53 +00:00
|
|
|
|
2020-07-14 15:29:13 +00:00
|
|
|
return morsel;
|
2000-11-05 17:54:41 +00:00
|
|
|
}
|
2017-05-08 17:15:51 +00:00
|
|
|
#endif /* ENABLE_TABCOMP */
|