Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Recoverable delete

7 views
Skip to first unread message

Ken Greer

unread,
Nov 4, 1984, 12:39:27 PM11/4/84
to
I've gotten several requests for this. The source
attached implements a recoverable file delete.


: Run this file as a shell script to extract contents.
: This file contains files Makefile README del.1 del.c expunge skulker

echo x - Makefile
cat << '//E*O*F Makefile' > Makefile
SHELL=/bin/sh
CC = /bin/cc
CFLAGS = -O
B = /usr/local/bin

del: del.o; $(CC) $(CFLAGS) -o $@ del.o

install inst-del: del
-/bin/rm $B/del $B/undel
mv del $B
ln $B/del $B/undel
@echo '*** remember to install the nightly skulker ***'

FILES = Makefile README del.1 del.c expunge skulker
sar: $(FILES)
sar $(FILES) > /tmp/del.sar

del.c: del.c,v; co -q del.c
//E*O*F Makefile
echo x - README
cat << '//E*O*F README' > README
The shell script "skulker" should be run from cron in the wee hours.
The shell script "expunge" is run from skulker.

Ken Greer
...!ucbvax!hplabs!kg (uucp)
kg@HP-Labs (CSNET)
kg.HP-Labs@Rand-Relay (ARPANET)
//E*O*F README
echo x - del.1
cat << '//E*O*F del.1' > del.1
.TH DEL 1 local
.SH NAME
del, undel \- recoverable file delete, undelete
.SH SYNOPSIS
\fBdel\fR [\fB-i -f -k\fR# \fB] \fIfiles...
.br
\fBundel \fIfiles...
.SH DESCRIPTION
.I Del
implements a recoverable file delete command.
The files, which are arguments to
.I del
are moved, rather than expunged, into an invisible directory.
A subsequent
.I undel
of a
\fIdel\fR-ed
file will recover it.
.PP
.I Del
works by relinking the file into a directory named
.I .gone
in the same directory as the original file.
The .gone directory is created, if necessary.
Files are marked with deletion time and date which defaults to now plus
24 hours.
(Only files which have a single link will have their times changed.)
Your
\fIdel\fR-ed
files will be expunged from the disk on that time or later by a nightly skulker.
Thus, files may be recovered at any time up to their expunge time,
independent of intervening logouts.
.PP
If the a file is not writable by you, \fIdel\fR prompts with the message:
.PP
override protection \fInnn\fR for file \fIfilename\fR?
.PP
A response of 'y' or 'Y' causes the file to be deleted.
Anything else leaves the file alone.
The switch -f, for force, turns off this query.
It is also turned off is the standard input is not a terminal.
.PP
The optional switches are:
.TP
-i
Interactive. \fIDel\fR will ask before deleting any file.
.TP
-f
Force. \fIDel\fR won't bother to ask even if the file is not writable by you.
.TP
-k
Keep time.
The number immediately following the -k is the number of days to keep
the file. Thus, -k3 keeps the file for (at least) 3 days, instead of the
default 1.
.PP
Most people find it useful to place the following aliases in their .cshrc file:
.PP
alias rm del
.br
alias rm! /bin/rm
.PP
.SH FILES
Deleted files are moved to the directory ".gone".
.SH SEE ALSO
rm (1)
.SH BUGS
You cannot
.I del
a directory.
Undel-ed files get their accessed and updated times set to the time
of undel-ing.
Files which have more than one link to them intentionally do not have their
times modified.
Therefore, these links are expunged at arbitrary times.
But, of course,
your del-ed file (link) takes no extra disk space while it stays around.
.SH AUTHOR
Ken Greer
//E*O*F del.1
echo x - del.c
cat << '//E*O*F del.c' > del.c
static char *RCSid =
"$Header: /usr/local/src/cmd/del/del.c,v 1.9 83/08/25 11:01:49 kgsu Exp $";

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>

/*
* DEL and UNDEL virtual delete and undelete
* Ken Greer
*/

#define equal(a, b) (strcmp(a, b) == 0)
#define DAY (60L * 60L * 24L) /* Seconds in a day */
#define FILESIZE 512

extern time_t time ();

typedef enum {FALSE = 0, TRUE = 1} BOOL;

typedef struct
{
time_t keep; /* -k: time to keep file around */
BOOL ask; /* -i: Always ask before deleting */
BOOL force; /* -f: don't ask even if no write permissions */
} OPTIONS;

/*
* Return pointer to the final file name in a path.
* I.e. sname ("/foo/baz/mumble")
* returns a pointer to "mumble".
*/
char *
sname (s)
register char *s;
{
register char *p;

for (p = s; *p;)
if (*p++ == '/')
s = p;
return (s);
}

mkdir (dir)
char *dir;
{
switch (vfork ())
{
case -1:
perror ("cannot create .gone directory");
exit (-1);
case 0: /* child side */
execl ("/bin/mkdir", "mkdir", dir, 0);
fprintf (stderr, "Cannot find /bin/mkdir\n");
_exit (-1);
default: /* parent side */
wait (0);
}
}

gone_names (file, gone_dir, gone_file)
char *file, *gone_dir, *gone_file;
{
extern char *rindex ();
register char *g = gone_dir, *start_of_name = file, *last_slash;
if (last_slash = rindex (file, '/'))
{
register char *p;
for (p = file; p <= last_slash; *g++ = *p++);
start_of_name = &last_slash[1];
}
strcpy (g, ".gone");
sprintf (gone_file, "%s/%s", gone_dir, start_of_name);
}

undelete (file)
char *file;
{
char gone_dir[FILESIZE], gone_file[FILESIZE];
time_t timep[2];
struct stat statb;

gone_names (file, gone_dir, gone_file);
if (link (gone_file, file) < 0)
{
fprintf (stderr, "%s: not found\n", gone_file);
return (-1);
}

if (unlink (gone_file) < 0)
{
fprintf (stderr, "%s: not removed\n", gone_file);
return (-1);
}

/*
* Reset accessed and updated time to now
* since we have already changed it to some time in the
* future when we "del"-ed it.
* But only if file has no other links to it.
*/
if(stat(file, &statb) == 0 && statb.st_nlink == 1)
{
timep[0] = timep[1] = time (0);
utime (file, timep);
}

return (0);
}

yes ()
{
int a, b;

while ((a = getchar ()) <= ' ' && a != EOF && a != '\n');
if (a != EOF && a != '\n')
while ((b = getchar ()) != EOF && b != '\n');

return (a == 'y' || a == 'Y');
}

delete (file, options)
char *file;
OPTIONS options;
{
char gone_dir[FILESIZE], gone_file[FILESIZE];
time_t timep[2];
struct stat statb;

gone_names (file, gone_dir, gone_file);
if(stat(file, &statb) < 0)
{
fprintf (stderr, "del: %s nonexistent\n", file);
return (-1);
}

if ((statb.st_mode&S_IFMT) == S_IFDIR)
{
fprintf (stderr, "del: %s is a directory; not removed\n", file);
return(-1);
}

if (options.ask) /* interactive */
{
printf ("del: remove %s? ", file);
if (!yes ())
return;
}
if(!options.force && access(file, 02) < 0) /* no write permission */
{
printf("del: override protection %o for %s? ",
statb.st_mode & 0777, file);
if (!yes ())
return;
}
if (access (gone_dir, 0) < 0)
mkdir (gone_dir);
unlink (gone_file);
if (link (file, gone_file) < 0)
{
fprintf (stderr, "del: %s not removed (link failed)\n", file);
return (-1);
}
if (unlink (file) < 0)
{
fprintf (stderr, "%s: not removed (unlink failed)\n", file);
return (-1);
}
/*
* Set the gone file's accessed time and updated time
* to the current time plus number of days to keep.
* Only do it if file has no other links to it.
*/
if (statb.st_nlink == 1)
{
timep[0] = timep[1] = time (0) + options.keep;
utime (gone_file, timep);
}

return (0);
}

main (argc, argv)
char **argv;
{
extern char *sname ();
register char *myname = sname (argv[0]);
register int i, failed = 0, del;
OPTIONS options;

options.keep = 1 * DAY;
options.ask = FALSE;
options.force = FALSE;

if (isatty(0) == 0)
options.force = TRUE;

for (; argc > 1 && argv[1][0] == '-'; argc--, argv++)
{
switch (argv[1][1])
{
case 'k': /* Keep this many days */
if (!isdigit (argv[1][2]))
{
fprintf (stderr, "Numeric parameter after -k required.\n");
exit (1);
}
options.keep = atoi (&argv[1][2]) * DAY;
break;
case 'f': /* force (as in rm and mv) */
options.force = TRUE;
break;
case 'i': /* interactive */
options.ask = TRUE;
break;
default:
fprintf(stderr,"%s: unknown switch \"%s\".\n", myname, argv[1]);
exit (1);
}
}
if (argc < 2)
fprintf (stderr, "Usage: %s files...\n", myname), exit (1);

del = equal (myname, "del");
for (i = 1; i < argc; i++)
failed |= del ? delete (argv[i], options) : undelete (argv[i]);
return (failed);
}
//E*O*F del.c
echo x - expunge
cat << '//E*O*F expunge' > expunge
#! /bin/csh -fe
# Run from "skulker" for the del command.
# Remove files older than current time/date.
if ($#argv < 1) exit 1
cd $1
cp /dev/null /tmp/now$$
find . -type f \! -newer /tmp/now$$ -exec /bin/rm -f {} \;
rm -f /tmp/now$$
cd .. && rmdir $1
//E*O*F expunge
echo x - skulker
cat << '//E*O*F skulker' > skulker
#! /bin/sh
# 'For the del command...'
find / -type d -name .gone -exec /usr/local/etc/expunge {} \;
//E*O*F skulker

--
Ken Greer
kg.hplabs@CSNET-Relay (ARPA)
kg@HP-Labs (CSNET)
hplabs!kg (UUCP)

Ken Arnold%UCB

unread,
Nov 7, 1984, 4:25:32 PM11/7/84
to
The following is an upgraded version of ctags which adds recognition
of YACC and lex files. For YACC files, it makes an entry for each
defined non-terminal, plus for all functions after the second "%%".
For lex, it just tags the functions after the second "%%" (what would
the tag be for a regular expression?). It considers ".y" files to
be YACC files. ".l" files are lisp files if their first non-blank
character is `;', `(', or '[' -- otherwise they are considered
lex files. Recognition of lisp files already existed in ctags.

The manual page follows separately.

Ken Arnold

--------------------cut here--------------------
static char *sccsid = "@(#)ctags.c 4.8 (Berkeley) 8/18/83";

#include <stdio.h>
#include <ctype.h>

/*
* ctags: create a tags file
*/

#define reg register
#define bool char

#define TRUE (1)
#define FALSE (0)

#define iswhite(arg) (_wht[arg]) /* T if char is white */
#define begtoken(arg) (_btk[arg]) /* T if char can start token */
#define intoken(arg) (_itk[arg]) /* T if char can be in token */
#define endtoken(arg) (_etk[arg]) /* T if char ends tokens */
#define isgood(arg) (_gd[arg]) /* T if char can be after ')' */

#define max(I1,I2) (I1 > I2 ? I1 : I2)

struct nd_st { /* sorting structure */
char *entry; /* function or type name */
char *file; /* file name */
bool f; /* use pattern or line no */
int lno; /* for -x option */
char *pat; /* search pattern */
bool been_warned; /* set if noticed dup */
struct nd_st *left,*right; /* left and right sons */
};

long ftell();
typedef struct nd_st NODE;

bool number, /* T if on line starting with # */
term = FALSE, /* T if print on terminal */
makefile= TRUE, /* T if to creat "tags" file */
gotone, /* found a func already on line */
/* boolean "func" (see init) */
_wht[0177],_etk[0177],_itk[0177],_btk[0177],_gd[0177];

/* typedefs are recognized using a simple finite automata,
* tydef is its state variable.
*/
typedef enum {none, begin, middle, end } TYST;

TYST tydef = none;

char searchar = '/'; /* use /.../ searches */

int lineno; /* line number of current line */
char line[4*BUFSIZ], /* current input line */
*curfile, /* current input file name */
*outfile= "tags", /* output file */
*white = " \f\t\n", /* white chars */
*endtk = " \t\n\"'#()[]{}=-+%*/&|^~!<>;,.:?",
/* token ending chars */
*begtk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz",
/* token starting chars */
*intk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz0123456789",
/* valid in-token chars */
*notgd = ",;"; /* non-valid after-function chars */

int file_num; /* current file number */
int aflag; /* -a: append to tags */
int tflag; /* -t: create tags for typedefs */
int uflag; /* -u: update tags */
int wflag; /* -w: suppress warnings */
int vflag; /* -v: create vgrind style index output */
int xflag; /* -x: create cxref style output */

char lbuf[BUFSIZ];

FILE *inf, /* ioptr for current input file */
*outf; /* ioptr for tags file */

long lineftell; /* ftell after getc( inf ) == '\n' */

NODE *head; /* the head of the sorted binary tree */

char *savestr();
char *rindex(), *index();
char *toss_comment();

main(ac,av)
int ac;
char *av[];
{
char cmd[100];
int i;

while (ac > 1 && av[1][0] == '-') {
for (i=1; av[1][i]; i++) {
switch(av[1][i]) {
case 'B':
searchar='?';
break;
case 'F':
searchar='/';
break;
case 'a':
aflag++;
break;
case 't':
tflag++;
break;
case 'u':
uflag++;
break;
case 'w':
wflag++;
break;
case 'v':
vflag++;
xflag++;
break;
case 'x':
xflag++;
break;
default:
goto usage;
}
}
ac--; av++;
}

if (ac <= 1) {
usage:
printf("Usage: ctags [-BFatuwvx] file ...\n");
exit(1);
}

init(); /* set up boolean "functions" */
/*
* loop through files finding functions
*/
for (file_num = 1; file_num < ac; file_num++)
find_entries(av[file_num]);

if (xflag) {
put_entries(head);
exit(0);
}
if (uflag) {
for (i=1; i<ac; i++) {
sprintf(cmd,
"mv %s OTAGS;fgrep -v '\t%s\t' OTAGS >%s;rm OTAGS",
outfile, av[i], outfile);
system(cmd);
}
aflag++;
}
outf = fopen(outfile, aflag ? "a" : "w");
if (outf == NULL) {
perror(outfile);
exit(1);
}
put_entries(head);
fclose(outf);
if (uflag) {
sprintf(cmd, "sort %s -o %s", outfile, outfile);
system(cmd);
}
exit(0);
}

/*
* This routine sets up the boolean psuedo-functions which work
* by seting boolean flags dependent upon the corresponding character
* Every char which is NOT in that string is not a white char. Therefore,
* all of the array "_wht" is set to FALSE, and then the elements
* subscripted by the chars in "white" are set to TRUE. Thus "_wht"
* of a char is TRUE if it is the string "white", else FALSE.
*/
init()
{

reg char *sp;
reg int i;

for (i = 0; i < 0177; i++) {
_wht[i] = _etk[i] = _itk[i] = _btk[i] = FALSE;
_gd[i] = TRUE;
}
for (sp = white; *sp; sp++)
_wht[*sp] = TRUE;
for (sp = endtk; *sp; sp++)
_etk[*sp] = TRUE;
for (sp = intk; *sp; sp++)
_itk[*sp] = TRUE;
for (sp = begtk; *sp; sp++)
_btk[*sp] = TRUE;
for (sp = notgd; *sp; sp++)
_gd[*sp] = FALSE;
}

/*
* This routine opens the specified file and calls the function
* which finds the function and type definitions.
*/
find_entries(file)
char *file;
{
char *cp;

if ((inf = fopen(file,"r")) == NULL) {
perror(file);
return;
}
curfile = savestr(file);
lineno = 0;
cp = rindex(file, '.');
/* .l implies lisp or lex source code */
if (cp && cp[1] == 'l' && cp[2] == '\0') {
if (index(first_char(), ";([") != NULL) { /* lisp */
L_funcs(inf);
fclose(inf);
return;
}
else { /* lex */
/*
* throw away all the code before the second "%%"
*/
toss_yysec();
getline();
pfnote("yylex", lineno, TRUE);
toss_yysec();
C_entries();
fclose(inf);
return;
}
}
/* .y implies a yacc file */
if (cp && cp[1] == 'y' && cp[2] == '\0') {
toss_yysec();
Y_entries();
C_entries();
fclose(inf);
return;
}
/* if not a .c or .h file, try fortran */
if (cp && (cp[1] != 'c' && cp[1] != 'h') && cp[2] == '\0') {
if (PF_funcs(inf) != 0) {
fclose(inf);
return;
}
rewind(inf); /* no fortran tags found, try C */
}
C_entries();
fclose(inf);
}

pfnote(name, ln, f)
char *name;
int ln;
bool f; /* f == TRUE when function */
{
register char *fp;
register NODE *np;
char nbuf[BUFSIZ];

if ((np = (NODE *) malloc(sizeof (NODE))) == NULL) {
fprintf(stderr, "ctags: too many entries to sort\n");
put_entries(head);
free_tree(head);
head = np = (NODE *) malloc(sizeof (NODE));
}
if (xflag == 0 && !strcmp(name, "main")) {
fp = rindex(curfile, '/');
if (fp == 0)
fp = curfile;
else
fp++;
sprintf(nbuf, "M%s", fp);
fp = rindex(nbuf, '.');
if (fp && fp[2] == 0)
*fp = 0;
name = nbuf;
}
np->entry = savestr(name);
np->file = curfile;
np->f = f;
np->lno = ln;
np->left = np->right = 0;
if (xflag == 0) {
lbuf[50] = 0;
strcat(lbuf, "$");
lbuf[50] = 0;
}
np->pat = savestr(lbuf);
if (head == NULL)
head = np;
else
add_node(np, head);
}

/*
* This routine finds functions and typedefs in C syntax and adds them
* to the list.
*/
C_entries()
{
register int c;
register char *token, *tp;
bool incomm, inquote, inchar, midtoken;
int level;
char *sp;
char tok[BUFSIZ];

number = gotone = midtoken = inquote = inchar = incomm = FALSE;
level = 0;
sp = tp = token = line;
lineno++;
lineftell = ftell(inf);
for (;;) {
*sp = c = getc(inf);
if (feof(inf))
break;
if (c == '\n')
lineno++;
else if (c == '\\') {
c = *++sp = getc(inf);
if (c = '\n')
c = ' ';
}
else if (incomm) {
if (c == '*') {
while ((*++sp=c=getc(inf)) == '*')
continue;
if (c == '\n')
lineno++;
if (c == '/')
incomm = FALSE;
}
}
else if (inquote) {
/*
* Too dumb to know about \" not being magic, but
* they usually occur in pairs anyway.
*/
if (c == '"')
inquote = FALSE;
continue;
}
else if (inchar) {
if (c == '\'')
inchar = FALSE;
continue;
}
else switch (c) {
case '"':
inquote = TRUE;
continue;
case '\'':
inchar = TRUE;
continue;
case '/':
if ((*++sp=c=getc(inf)) == '*')
incomm = TRUE;
else
ungetc(*sp, inf);
continue;
case '#':
if (sp == line)
number = TRUE;
continue;
case '{':
if (tydef == begin) {
tydef=middle;
}
level++;
continue;
case '}':
if (sp == line)
level = 0; /* reset */
else
level--;
if (!level && tydef==middle) {
tydef=end;
}
continue;
}
if (!level && !inquote && !incomm && gotone == FALSE) {
if (midtoken) {
if (endtoken(c)) {
int f;
int pfline = lineno;
if (start_entry(&sp,token,tp,&f)) {
strncpy(tok,token,tp-token+1);
tok[tp-token+1] = 0;
getline();
pfnote(tok, pfline, f);
gotone = f; /* function */
}
midtoken = FALSE;
token = sp;
}
else if (intoken(c))
tp++;
}
else if (begtoken(c)) {
token = tp = sp;
midtoken = TRUE;
}
}
if (c == ';' && tydef==end) /* clean with typedefs */
tydef=none;
sp++;
if (c == '\n' || sp > &line[sizeof (line) - BUFSIZ]) {
tp = token = sp = line;
lineftell = ftell(inf);
number = gotone = midtoken = inquote = inchar = FALSE;
}
}
}

/*
* This routine checks to see if the current token is
* at the start of a function, or corresponds to a typedef
* It updates the input line * so that the '(' will be
* in it when it returns.
*/
start_entry(lp,token,tp,f)
char **lp,*token,*tp;
int *f;
{
reg char c,*sp,*tsp;
static bool found;
bool firsttok; /* T if have seen first token in ()'s */
int bad;

*f = 1; /* a function */
sp = *lp;
c = *sp;
bad = FALSE;
if (!number) { /* space is not allowed in macro defs */
while (iswhite(c)) {
*++sp = c = getc(inf);
if (c == '\n') {
lineno++;
if (sp > &line[sizeof (line) - BUFSIZ])
goto ret;
}
}
/* the following tries to make it so that a #define a b(c) */
/* doesn't count as a define of b. */
}
else {
if (!strncmp(token, "define", 6))
found = 0;
else
found++;
if (found >= 2) {
gotone = TRUE;
badone: bad = TRUE;
goto ret;
}
}
/* check for the typedef cases */
if (tflag && !strncmp(token, "typedef", 7)) {
tydef=begin;
goto badone;
}
if (tydef==begin && (!strncmp(token, "struct", 6) ||
!strncmp(token, "union", 5) || !strncmp(token, "enum", 4))) {
goto badone;
}
if (tydef==begin) {
tydef=end;
goto badone;
}
if (tydef==end) {
*f = 0;
goto ret;
}
if (c != '(')
goto badone;
firsttok = FALSE;
while ((*++sp=c=getc(inf)) != ')') {
if (c == '\n') {
lineno++;
if (sp > &line[sizeof (line) - BUFSIZ])
goto ret;
}
/*
* This line used to confuse ctags:
* int (*oldhup)();
* This fixes it. A nonwhite char before the first
* token, other than a / (in case of a comment in there)
* makes this not a declaration.
*/
if (begtoken(c) || c=='/')
firsttok++;
else if (!iswhite(c) && !firsttok)
goto badone;
}
while (iswhite(*++sp=c=getc(inf)))
if (c == '\n') {
lineno++;
if (sp > &line[sizeof (line) - BUFSIZ])
break;
}
ret:
*lp = --sp;
if (c == '\n')
lineno--;
ungetc(c,inf);
return !bad && (!*f || isgood(c));
/* hack for typedefs */
}

/*
* Y_entries:
* Find the yacc tags and put them in.
*/
Y_entries()
{
register char *sp, *orig_sp;
register int brace;
register bool in_rule, toklen;
char tok[BUFSIZ];

brace = 0;
getline();
pfnote("yyparse", lineno, TRUE);
while (fgets(line, sizeof line, inf) != NULL)
for (sp = line; *sp; sp++)
switch (*sp) {
case '\n':
lineno++;
/* FALLTHROUGH */
case ' ':
case '\t':
case '\f':
case '\r':
break;
case '"':
do {
while (*++sp != '"')
continue;
} while (sp[-1] == '\\');
break;
case '\'':
do {
while (*++sp != '\'')
continue;
} while (sp[-1] == '\\');
break;
case '/':
if (*++sp == '*')
sp = toss_comment(sp);
else
--sp;
break;
case '{':
brace++;
break;
case '}':
brace--;
break;
case '%':
if (sp[1] == '%' && sp == line)
return;
break;
case '|':
case ';':
in_rule = FALSE;
break;
default:
if (brace == 0 && !in_rule && (isalpha(*sp) ||
*sp == '.' ||
*sp == '_')) {
orig_sp = sp;
++sp;
while (isalnum(*sp) || *sp == '_' ||
*sp == '.')
sp++;
toklen = sp - orig_sp;
while (isspace(*sp))
sp++;
if (*sp == ':' || (*sp == '\0' &&
first_char() == ':'))
{
strncpy(tok, orig_sp, toklen);
tok[toklen] = '\0';
strcpy(lbuf, line);
lbuf[strlen(lbuf) - 1] = '\0';
pfnote(tok, lineno, TRUE);
in_rule = TRUE;
}
else
sp--;
}
break;
}
}

char *
toss_comment(start)
char *start;
{
register char *sp;

/*
* first, see if the end-of-comment is on the same line
*/
do {
while ((sp = index(start, '*')) != NULL)
if (sp[1] == '/')
return ++sp;
else
start = ++sp;
start = line;
lineno++;
} while (fgets(line, sizeof line, inf) != NULL);
}

getline()
{
long saveftell = ftell( inf );
register char *cp;

fseek( inf , lineftell , 0 );
fgets(lbuf, sizeof lbuf, inf);
cp = rindex(lbuf, '\n');
if (cp)
*cp = 0;
fseek(inf, saveftell, 0);
}

free_tree(node)
NODE *node;
{

while (node) {
free_tree(node->right);
cfree(node);
node = node->left;
}
}

add_node(node, cur_node)
NODE *node,*cur_node;
{
register int dif;

dif = strcmp(node->entry, cur_node->entry);
if (dif == 0) {
if (node->file == cur_node->file) {
if (!wflag) {
fprintf(stderr,"Duplicate entry in file %s, line %d: %s\n",
node->file,lineno,node->entry);
fprintf(stderr,"Second entry ignored\n");
}
return;
}
if (!cur_node->been_warned)
if (!wflag)
fprintf(stderr,"Duplicate entry in files %s and %s: %s (Warning only)\n",
node->file, cur_node->file, node->entry);
cur_node->been_warned = TRUE;
return;
}

if (dif < 0) {
if (cur_node->left != NULL)
add_node(node,cur_node->left);
else
cur_node->left = node;
return;
}
if (cur_node->right != NULL)
add_node(node,cur_node->right);
else
cur_node->right = node;
}

put_entries(node)
reg NODE *node;
{
reg char *sp;

if (node == NULL)
return;
put_entries(node->left);
if (xflag == 0)
if (node->f) { /* a function */
fprintf(outf, "%s\t%s\t%c^",
node->entry, node->file, searchar);
for (sp = node->pat; *sp; sp++)
if (*sp == '\\')
fprintf(outf, "\\\\");
else if (*sp == searchar)
fprintf(outf, "\\%c", searchar);
else
putc(*sp, outf);
fprintf(outf, "%c\n", searchar);
}
else { /* a typedef; text pattern inadequate */
fprintf(outf, "%s\t%s\t%d\n",
node->entry, node->file, node->lno);
}
else if (vflag)
fprintf(stdout, "%s %s %d\n",
node->entry, node->file, (node->lno+63)/64);
else
fprintf(stdout, "%-16s%4d %-16s %s\n",
node->entry, node->lno, node->file, node->pat);
put_entries(node->right);
}
char *dbp = lbuf;
int pfcnt;

PF_funcs(fi)
FILE *fi;
{

pfcnt = 0;
while (fgets(lbuf, sizeof(lbuf), fi)) {
lineno++;
dbp = lbuf;
if ( *dbp == '%' ) dbp++ ; /* Ratfor escape to fortran */
while (isspace(*dbp))
dbp++;
if (*dbp == 0)
continue;
switch (*dbp |' ') {

case 'i':
if (tail("integer"))
takeprec();
break;
case 'r':
if (tail("real"))
takeprec();
break;
case 'l':
if (tail("logical"))
takeprec();
break;
case 'c':
if (tail("complex") || tail("character"))
takeprec();
break;
case 'd':
if (tail("double")) {
while (isspace(*dbp))
dbp++;
if (*dbp == 0)
continue;
if (tail("precision"))
break;
continue;
}
break;
}
while (isspace(*dbp))
dbp++;
if (*dbp == 0)
continue;
switch (*dbp|' ') {

case 'f':
if (tail("function"))
getit();
continue;
case 's':
if (tail("subroutine"))
getit();
continue;
case 'p':
if (tail("program")) {
getit();
continue;
}
if (tail("procedure"))
getit();
continue;
}
}
return (pfcnt);
}

tail(cp)
char *cp;
{
register int len = 0;

while (*cp && (*cp&~' ') == ((*(dbp+len))&~' '))
cp++, len++;
if (*cp == 0) {
dbp += len;
return (1);
}
return (0);
}

takeprec()
{

while (isspace(*dbp))
dbp++;
if (*dbp != '*')
return;
dbp++;
while (isspace(*dbp))
dbp++;
if (!isdigit(*dbp)) {
--dbp; /* force failure */
return;
}
do
dbp++;
while (isdigit(*dbp));
}

getit()
{
register char *cp;
char c;
char nambuf[BUFSIZ];

for (cp = lbuf; *cp; cp++)
;
*--cp = 0; /* zap newline */
while (isspace(*dbp))
dbp++;
if (*dbp == 0 || !isalpha(*dbp))
return;
for (cp = dbp+1; *cp && (isalpha(*cp) || isdigit(*cp)); cp++)
continue;
c = cp[0];
cp[0] = 0;
strcpy(nambuf, dbp);
cp[0] = c;
pfnote(nambuf, lineno);
pfcnt++;
}

char *
savestr(cp)
char *cp;
{
register int len;
register char *dp;

len = strlen(cp);
dp = (char *)malloc(len+1);
strcpy(dp, cp);
return (dp);
}

/*
* Return the ptr in sp at which the character c last
* appears; NULL if not found
*
* Identical to v7 rindex, included for portability.
*/

char *
rindex(sp, c)
register char *sp, c;
{
register char *r;

r = NULL;
do {
if (*sp == c)
r = sp;
} while (*sp++);
return(r);
}

/*
* lisp tag functions
* just look for (def or (DEF
*/

L_funcs (fi)
FILE *fi;
{
pfcnt = 0;
while (fgets(lbuf, sizeof(lbuf), fi)) {
lineno++;
dbp = lbuf;
if (dbp[0] == '(' &&
(dbp[1] == 'D' || dbp[1] == 'd') &&
(dbp[2] == 'E' || dbp[2] == 'e') &&
(dbp[3] == 'F' || dbp[3] == 'f')) {
while (!isspace(*dbp)) dbp++;
while (isspace(*dbp)) dbp++;
L_getit();
}
}
}

L_getit()
{
register char *cp;
char c;
char nambuf[BUFSIZ];

for (cp = lbuf; *cp; cp++) ;
*--cp = 0; /* zap newline */
if (*dbp == 0) return;
for (cp = dbp+1; *cp && *cp != '(' && *cp != ' '; cp++)
continue;
c = cp[0];
cp[0] = 0;
strcpy(nambuf, dbp);
cp[0] = c;
pfnote(nambuf, lineno,TRUE);
pfcnt++;
}

/*
* first_char:
* Return the first non-blank character in the file. After
* finding it, rewind the input file so we start at the beginning
* again.
*/
first_char()
{
register int c;
register long off;

off = ftell(inf);
while ((c = getc(inf)) != EOF)
if (c != ' ' && c != '\t' && c != '\f' && c != '\n' &&
c != '\r') {
fseek(inf, off, 0);
return c;
}
fseek(inf, off, 0);
return EOF;
}

/*
* toss_yysec:
* Toss away code until the next "%%" line.
*/
toss_yysec()
{
char buf[BUFSIZ];

for (;;) {
lineftell = ftell(inf);
if (fgets(buf, BUFSIZ, inf) == NULL)
return;
lineno++;
if (strncmp(buf, "%%", 2) == 0)
return;
}
}

Ken Arnold%UCB

unread,
Nov 7, 1984, 4:27:39 PM11/7/84
to
This is the manual page for a version of ctags which recognizes
YACC and lex input. The source has been posted separately with
a summary of the added capability.

Ken Arnold

--------------------cut here--------------------
.TH CTAGS 1 "25 August 1982"
.UC 4
.SH NAME
ctags \- create a tags file
.SH SYNOPSIS
.B ctags
[
.B \-BFatuwvx
]
name ...
.SH DESCRIPTION
.I Ctags
makes a tags file for
.IR ex (1)
from the specified C, Pascal, Fortran, YACC, lex, and lisp sources.
A tags file gives the locations of specified objects (in this case
functions and typedefs) in a group of files. Each line of the tags
file contains the object name, the file in which it is defined, and
an address specification for the object definition. Functions are
searched with a pattern, typedefs with a line number. Specifiers are
given in separate fields on the line, separated by blanks or tabs.
Using the
.I tags
file,
.I ex
can quickly find these objects definitions.
.PP
If the
.B \-x
flag is given,
.I ctags
produces a list of object names, the line number and file
name on which each is defined, as well as the text of that line
and prints this on the standard output. This is a simple index
which can be printed out as an off-line readable function index.
.PP
If the
.B \-v
flag is given,
an index of the form expected by
.IR vgrind (1)
is produced on the standard output.
This listing contains the function name,
file name, and page number
(assuming 64 line pages).
Since the output will be sorted into lexicographic order,
it may be desired to run the output through
.BR "sort \-f" .
Sample use:
.nf
ctags \-v files | sort \-f > index
vgrind \-x index
.fi
.PP
Files whose names end in
.B \.c
or
.B \.h
are assumed to be C source files and are searched for C routine and
macro definitions.
Files whose names end in
.B \.y
are assumed to be YACC source files.
Files whose names end in
.B \.l
are assumed to be either lisp files
if their first non-blank character is `;', `(', or `[',
or lex files otherwise.
Other files are first examined to see if they contain any Pascal or
Fortran routine definitions; if not, they are processed again
looking for C definitions.
.PP
Other options are:
.TP 5
.B \-F
use forward searching patterns (/.../) (default).
.TP 5
.B \-B
use backward searching patterns (?...?).
.TP 5
.B \-a
append to tags file.
.TP 5
.B \-t
create tags for typedefs.
.TP 5
.B \-w
suppressing warning diagnostics.
.TP 5
.B \-u
causing the specified files to be
.I updated
in tags, that is, all references to them are deleted,
and the new values are appended to the file.
(Beware: this option is implemented in a way which is rather slow;
it is usually faster to simply rebuild the
.I tags
file.)
.PP
The tag
.I main
is treated specially in C programs.
The tag formed is created by prepending
.I M
to the name of the file, with a trailing .c removed, if
any, and leading pathname components also removed.
This makes use of
.I ctags
practical in directories with more than one program.
.SH FILES
.DT
tags output tags file
.SH SEE ALSO
ex(1), vi(1)
.SH AUTHOR
Ken Arnold; FORTRAN added by Jim Kleckner; Bill Joy
added Pascal and
.B \-x,
replacing
.I cxref;
C typedefs added by Ed Pelegri-Llopart.
.SH BUGS
Recognition of \fBfunctions\fR, \fBsubroutines\fR and \fBprocedures\fR
for FORTRAN and Pascal is done is a very simpleminded way.
No attempt is made to deal with block structure; if you have two
Pascal procedures in different blocks with the same name you lose.
.PP
The method of deciding whether to look for C or Pascal and FORTRAN
functions is a hack.
.PP
Does not know about #ifdefs.
.PP
Should know about Pascal types.
Relies on the input being well formed to detect typedefs.
Use of -tx shows only the last line of typedefs.

0 new messages