freebsd-dev/contrib/less/edit.c
Robert Watson 1ea316270f Currently, less(1) uses K&R prototypes, which both fails to provide useful
compiler-time type checking, and also causes problems for targets where
multiple incompatible calling conventions may be selected based on argument
types.  This change switches less(1) to ANSI prototypes.

While there, we also remove use of "register", and attempt to use "const" a
bit better now that the compiler can check argument types.

Reviewed by:	cem, emaste
MFC after:	3 weeks
Sponsored by:	DARPA, AFRL
Differential Revision:	https://reviews.freebsd.org/D10152
2017-03-31 21:29:43 +00:00

805 lines
15 KiB
C

/*
* Copyright (C) 1984-2015 Mark Nudelman
*
* You may distribute under the terms of either the GNU General Public
* License or the Less License, as specified in the README file.
*
* For more information, see the README file.
*/
#include "less.h"
#if HAVE_STAT
#include <sys/stat.h>
#endif
public int fd0 = 0;
extern int new_file;
extern int errmsgs;
extern int cbufs;
extern char *every_first_cmd;
extern int any_display;
extern int force_open;
extern int is_tty;
extern int sigs;
extern IFILE curr_ifile;
extern IFILE old_ifile;
extern struct scrpos initial_scrpos;
extern void * constant ml_examine;
#if SPACES_IN_FILENAMES
extern char openquote;
extern char closequote;
#endif
#if LOGFILE
extern int logfile;
extern int force_logfile;
extern char *namelogfile;
#endif
#if HAVE_STAT_INO
public dev_t curr_dev;
public ino_t curr_ino;
#endif
char *curr_altfilename = NULL;
static void *curr_altpipe;
/*
* Textlist functions deal with a list of words separated by spaces.
* init_textlist sets up a textlist structure.
* forw_textlist uses that structure to iterate thru the list of
* words, returning each one as a standard null-terminated string.
* back_textlist does the same, but runs thru the list backwards.
*/
public void
init_textlist(struct textlist *tlist, char *str)
{
char *s;
#if SPACES_IN_FILENAMES
int meta_quoted = 0;
int delim_quoted = 0;
char *esc = get_meta_escape();
int esclen = (int) strlen(esc);
#endif
tlist->string = skipsp(str);
tlist->endstring = tlist->string + strlen(tlist->string);
for (s = str; s < tlist->endstring; s++)
{
#if SPACES_IN_FILENAMES
if (meta_quoted)
{
meta_quoted = 0;
} else if (esclen > 0 && s + esclen < tlist->endstring &&
strncmp(s, esc, esclen) == 0)
{
meta_quoted = 1;
s += esclen - 1;
} else if (delim_quoted)
{
if (*s == closequote)
delim_quoted = 0;
} else /* (!delim_quoted) */
{
if (*s == openquote)
delim_quoted = 1;
else if (*s == ' ')
*s = '\0';
}
#else
if (*s == ' ')
*s = '\0';
#endif
}
}
public char *
forw_textlist(struct textlist *tlist, char *prev)
{
char *s;
/*
* prev == NULL means return the first word in the list.
* Otherwise, return the word after "prev".
*/
if (prev == NULL)
s = tlist->string;
else
s = prev + strlen(prev);
if (s >= tlist->endstring)
return (NULL);
while (*s == '\0')
s++;
if (s >= tlist->endstring)
return (NULL);
return (s);
}
public char *
back_textlist(struct textlist *tlist, char *prev)
{
char *s;
/*
* prev == NULL means return the last word in the list.
* Otherwise, return the word before "prev".
*/
if (prev == NULL)
s = tlist->endstring;
else if (prev <= tlist->string)
return (NULL);
else
s = prev - 1;
while (*s == '\0')
s--;
if (s <= tlist->string)
return (NULL);
while (s[-1] != '\0' && s > tlist->string)
s--;
return (s);
}
/*
* Close the current input file.
*/
static void
close_file(void)
{
struct scrpos scrpos;
if (curr_ifile == NULL_IFILE)
return;
/*
* Save the current position so that we can return to
* the same position if we edit this file again.
*/
get_scrpos(&scrpos);
if (scrpos.pos != NULL_POSITION)
{
store_pos(curr_ifile, &scrpos);
lastmark();
}
/*
* Close the file descriptor, unless it is a pipe.
*/
ch_close();
/*
* If we opened a file using an alternate name,
* do special stuff to close it.
*/
if (curr_altfilename != NULL)
{
close_altfile(curr_altfilename, get_filename(curr_ifile),
curr_altpipe);
free(curr_altfilename);
curr_altfilename = NULL;
}
curr_ifile = NULL_IFILE;
#if HAVE_STAT_INO
curr_ino = curr_dev = 0;
#endif
}
/*
* Edit a new file (given its name).
* Filename == "-" means standard input.
* Filename == NULL means just close the current file.
*/
public int
edit(char *filename)
{
if (filename == NULL)
return (edit_ifile(NULL_IFILE));
return (edit_ifile(get_ifile(filename, curr_ifile)));
}
/*
* Edit a new file (given its IFILE).
* ifile == NULL means just close the current file.
*/
public int
edit_ifile(IFILE ifile)
{
int f;
int answer;
int no_display;
int chflags;
char *filename;
char *open_filename;
char *qopen_filename;
char *alt_filename;
void *alt_pipe;
IFILE was_curr_ifile;
PARG parg;
if (ifile == curr_ifile)
{
/*
* Already have the correct file open.
*/
return (0);
}
/*
* We must close the currently open file now.
* This is necessary to make the open_altfile/close_altfile pairs
* nest properly (or rather to avoid nesting at all).
* {{ Some stupid implementations of popen() mess up if you do:
* fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
*/
#if LOGFILE
end_logfile();
#endif
was_curr_ifile = save_curr_ifile();
if (curr_ifile != NULL_IFILE)
{
chflags = ch_getflags();
close_file();
if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1)
{
/*
* Don't keep the help file in the ifile list.
*/
del_ifile(was_curr_ifile);
was_curr_ifile = old_ifile;
}
}
if (ifile == NULL_IFILE)
{
/*
* No new file to open.
* (Don't set old_ifile, because if you call edit_ifile(NULL),
* you're supposed to have saved curr_ifile yourself,
* and you'll restore it if necessary.)
*/
unsave_ifile(was_curr_ifile);
return (0);
}
filename = save(get_filename(ifile));
/*
* See if LESSOPEN specifies an "alternate" file to open.
*/
alt_pipe = NULL;
alt_filename = open_altfile(filename, &f, &alt_pipe);
open_filename = (alt_filename != NULL) ? alt_filename : filename;
qopen_filename = shell_unquote(open_filename);
chflags = 0;
if (alt_pipe != NULL)
{
/*
* The alternate "file" is actually a pipe.
* f has already been set to the file descriptor of the pipe
* in the call to open_altfile above.
* Keep the file descriptor open because it was opened
* via popen(), and pclose() wants to close it.
*/
chflags |= CH_POPENED;
} else if (strcmp(open_filename, "-") == 0)
{
/*
* Use standard input.
* Keep the file descriptor open because we can't reopen it.
*/
f = fd0;
chflags |= CH_KEEPOPEN;
/*
* Must switch stdin to BINARY mode.
*/
SET_BINARY(f);
#if MSDOS_COMPILER==DJGPPC
/*
* Setting stdin to binary by default causes
* Ctrl-C to not raise SIGINT. We must undo
* that side-effect.
*/
__djgpp_set_ctrl_c(1);
#endif
} else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0)
{
f = -1;
chflags |= CH_NODATA;
} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
{
f = -1;
chflags |= CH_HELPFILE;
} else if ((parg.p_string = bad_file(open_filename)) != NULL)
{
/*
* It looks like a bad file. Don't try to open it.
*/
error("%s", &parg);
free(parg.p_string);
err1:
if (alt_filename != NULL)
{
close_altfile(alt_filename, filename, alt_pipe);
free(alt_filename);
}
del_ifile(ifile);
free(qopen_filename);
free(filename);
/*
* Re-open the current file.
*/
if (was_curr_ifile == ifile)
{
/*
* Whoops. The "current" ifile is the one we just deleted.
* Just give up.
*/
quit(QUIT_ERROR);
}
reedit_ifile(was_curr_ifile);
return (1);
} else if ((f = open(qopen_filename, OPEN_READ)) < 0)
{
/*
* Got an error trying to open it.
*/
parg.p_string = errno_message(filename);
error("%s", &parg);
free(parg.p_string);
goto err1;
} else
{
chflags |= CH_CANSEEK;
if (!force_open && !opened(ifile) && bin_file(f))
{
/*
* Looks like a binary file.
* Ask user if we should proceed.
*/
parg.p_string = filename;
answer = query("\"%s\" may be a binary file. See it anyway? ",
&parg);
if (answer != 'y' && answer != 'Y')
{
close(f);
goto err1;
}
}
}
/*
* Get the new ifile.
* Get the saved position for the file.
*/
if (was_curr_ifile != NULL_IFILE)
{
old_ifile = was_curr_ifile;
unsave_ifile(was_curr_ifile);
}
curr_ifile = ifile;
curr_altfilename = alt_filename;
curr_altpipe = alt_pipe;
set_open(curr_ifile); /* File has been opened */
get_pos(curr_ifile, &initial_scrpos);
new_file = TRUE;
ch_init(f, chflags);
if (!(chflags & CH_HELPFILE))
{
#if LOGFILE
if (namelogfile != NULL && is_tty)
use_logfile(namelogfile);
#endif
#if HAVE_STAT_INO
/* Remember the i-number and device of the opened file. */
{
struct stat statbuf;
int r = stat(qopen_filename, &statbuf);
if (r == 0)
{
curr_ino = statbuf.st_ino;
curr_dev = statbuf.st_dev;
}
}
#endif
if (every_first_cmd != NULL)
{
ungetcc(CHAR_END_COMMAND);
ungetsc(every_first_cmd);
}
}
free(qopen_filename);
no_display = !any_display;
flush();
any_display = TRUE;
if (is_tty)
{
/*
* Output is to a real tty.
*/
/*
* Indicate there is nothing displayed yet.
*/
pos_clear();
clr_linenum();
#if HILITE_SEARCH
clr_hilite();
#endif
if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
cmd_addhist(ml_examine, filename, 1);
if (no_display && errmsgs > 0)
{
/*
* We displayed some messages on error output
* (file descriptor 2; see error() function).
* Before erasing the screen contents,
* display the file name and wait for a keystroke.
*/
parg.p_string = filename;
error("%s", &parg);
}
}
free(filename);
return (0);
}
/*
* Edit a space-separated list of files.
* For each filename in the list, enter it into the ifile list.
* Then edit the first one.
*/
public int
edit_list(char *filelist)
{
IFILE save_ifile;
char *good_filename;
char *filename;
char *gfilelist;
char *gfilename;
struct textlist tl_files;
struct textlist tl_gfiles;
save_ifile = save_curr_ifile();
good_filename = NULL;
/*
* Run thru each filename in the list.
* Try to glob the filename.
* If it doesn't expand, just try to open the filename.
* If it does expand, try to open each name in that list.
*/
init_textlist(&tl_files, filelist);
filename = NULL;
while ((filename = forw_textlist(&tl_files, filename)) != NULL)
{
gfilelist = lglob(filename);
init_textlist(&tl_gfiles, gfilelist);
gfilename = NULL;
while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
{
if (edit(gfilename) == 0 && good_filename == NULL)
good_filename = get_filename(curr_ifile);
}
free(gfilelist);
}
/*
* Edit the first valid filename in the list.
*/
if (good_filename == NULL)
{
unsave_ifile(save_ifile);
return (1);
}
if (get_ifile(good_filename, curr_ifile) == curr_ifile)
{
/*
* Trying to edit the current file; don't reopen it.
*/
unsave_ifile(save_ifile);
return (0);
}
reedit_ifile(save_ifile);
return (edit(good_filename));
}
/*
* Edit the first file in the command line (ifile) list.
*/
public int
edit_first(void)
{
curr_ifile = NULL_IFILE;
return (edit_next(1));
}
/*
* Edit the last file in the command line (ifile) list.
*/
public int
edit_last(void)
{
curr_ifile = NULL_IFILE;
return (edit_prev(1));
}
/*
* Edit the n-th next or previous file in the command line (ifile) list.
*/
static int
edit_istep(IFILE h, int n, int dir)
{
IFILE next;
/*
* Skip n filenames, then try to edit each filename.
*/
for (;;)
{
next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
if (--n < 0)
{
if (edit_ifile(h) == 0)
break;
}
if (next == NULL_IFILE)
{
/*
* Reached end of the ifile list.
*/
return (1);
}
if (ABORT_SIGS())
{
/*
* Interrupt breaks out, if we're in a long
* list of files that can't be opened.
*/
return (1);
}
h = next;
}
/*
* Found a file that we can edit.
*/
return (0);
}
static int
edit_inext(IFILE h, int n)
{
return (edit_istep(h, n, +1));
}
public int
edit_next(int n)
{
return edit_istep(curr_ifile, n, +1);
}
static int
edit_iprev(IFILE h, int n)
{
return (edit_istep(h, n, -1));
}
public int
edit_prev(int n)
{
return edit_istep(curr_ifile, n, -1);
}
/*
* Edit a specific file in the command line (ifile) list.
*/
public int
edit_index(int n)
{
IFILE h;
h = NULL_IFILE;
do
{
if ((h = next_ifile(h)) == NULL_IFILE)
{
/*
* Reached end of the list without finding it.
*/
return (1);
}
} while (get_index(h) != n);
return (edit_ifile(h));
}
public IFILE
save_curr_ifile(void)
{
if (curr_ifile != NULL_IFILE)
hold_ifile(curr_ifile, 1);
return (curr_ifile);
}
public void
unsave_ifile(IFILE save_ifile)
{
if (save_ifile != NULL_IFILE)
hold_ifile(save_ifile, -1);
}
/*
* Reedit the ifile which was previously open.
*/
public void
reedit_ifile(IFILE save_ifile)
{
IFILE next;
IFILE prev;
/*
* Try to reopen the ifile.
* Note that opening it may fail (maybe the file was removed),
* in which case the ifile will be deleted from the list.
* So save the next and prev ifiles first.
*/
unsave_ifile(save_ifile);
next = next_ifile(save_ifile);
prev = prev_ifile(save_ifile);
if (edit_ifile(save_ifile) == 0)
return;
/*
* If can't reopen it, open the next input file in the list.
*/
if (next != NULL_IFILE && edit_inext(next, 0) == 0)
return;
/*
* If can't open THAT one, open the previous input file in the list.
*/
if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
return;
/*
* If can't even open that, we're stuck. Just quit.
*/
quit(QUIT_ERROR);
}
public void
reopen_curr_ifile(void)
{
IFILE save_ifile = save_curr_ifile();
close_file();
reedit_ifile(save_ifile);
}
/*
* Edit standard input.
*/
public int
edit_stdin(void)
{
if (isatty(fd0))
{
error("Missing filename (\"less --help\" for help)", NULL_PARG);
quit(QUIT_OK);
}
return (edit("-"));
}
/*
* Copy a file directly to standard output.
* Used if standard output is not a tty.
*/
public void
cat_file(void)
{
int c;
while ((c = ch_forw_get()) != EOI)
putchr(c);
flush();
}
#if LOGFILE
/*
* If the user asked for a log file and our input file
* is standard input, create the log file.
* We take care not to blindly overwrite an existing file.
*/
public void
use_logfile(char *filename)
{
int exists;
int answer;
PARG parg;
if (ch_getflags() & CH_CANSEEK)
/*
* Can't currently use a log file on a file that can seek.
*/
return;
/*
* {{ We could use access() here. }}
*/
filename = shell_unquote(filename);
exists = open(filename, OPEN_READ);
if (exists >= 0)
close(exists);
exists = (exists >= 0);
/*
* Decide whether to overwrite the log file or append to it.
* If it doesn't exist we "overwrite" it.
*/
if (!exists || force_logfile)
{
/*
* Overwrite (or create) the log file.
*/
answer = 'O';
} else
{
/*
* Ask user what to do.
*/
parg.p_string = filename;
answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
}
loop:
switch (answer)
{
case 'O': case 'o':
/*
* Overwrite: create the file.
*/
logfile = creat(filename, 0644);
break;
case 'A': case 'a':
/*
* Append: open the file and seek to the end.
*/
logfile = open(filename, OPEN_APPEND);
if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK)
{
close(logfile);
logfile = -1;
}
break;
case 'D': case 'd':
/*
* Don't do anything.
*/
free(filename);
return;
case 'q':
quit(QUIT_OK);
/*NOTREACHED*/
default:
/*
* Eh?
*/
answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
goto loop;
}
if (logfile < 0)
{
/*
* Error in opening logfile.
*/
parg.p_string = filename;
error("Cannot write to \"%s\"", &parg);
free(filename);
return;
}
free(filename);
SET_BINARY(logfile);
}
#endif