Пример #1
0
void cgroup_get_chart_id(struct cgroup *cg) {
	debug(D_CGROUP, "getting the name of cgroup '%s'", cg->id);

	pid_t cgroup_pid;
	char buffer[CGROUP_CHARTID_LINE_MAX + 1];

	snprintfz(buffer, CGROUP_CHARTID_LINE_MAX, "exec %s '%s'",
	         config_get("plugin:cgroups", "script to get cgroup names", PLUGINS_DIR "/cgroup-name.sh"), cg->chart_id);

	debug(D_CGROUP, "executing command '%s' for cgroup '%s'", buffer, cg->id);
	FILE *fp = mypopen(buffer, &cgroup_pid);
	if(!fp) {
		error("CGROUP: Cannot popen(\"%s\", \"r\").", buffer);
		return;
	}
	debug(D_CGROUP, "reading from command '%s' for cgroup '%s'", buffer, cg->id);
	char *s = fgets(buffer, CGROUP_CHARTID_LINE_MAX, fp);
	debug(D_CGROUP, "closing command for cgroup '%s'", cg->id);
	mypclose(fp, cgroup_pid);
	debug(D_CGROUP, "closed command for cgroup '%s'", cg->id);

	if(s && *s && *s != '\n') {
		debug(D_CGROUP, "cgroup '%s' should be renamed to '%s'", cg->id, s);

		trim(s);

		free(cg->chart_title);
		cg->chart_title = strdup(s);
		if(!cg->chart_title)
			fatal("CGROUP: Cannot allocate memory for chart name of cgroup '%s' chart name: '%s'", cg->id, s);

		netdata_fix_chart_name(cg->chart_title);

		free(cg->chart_id);
		cg->chart_id = strdup(s);
		if(!cg->chart_id)
			fatal("CGROUP: Cannot allocate memory for chart id of cgroup '%s' chart id: '%s'", cg->id, s);

		netdata_fix_chart_id(cg->chart_id);

		debug(D_CGROUP, "cgroup '%s' renamed to '%s' (title: '%s')", cg->id, cg->chart_id, cg->chart_title);
	}
	else debug(D_CGROUP, "cgroup '%s' is not to be renamed (will be shown as '%s')", cg->id, cg->chart_id);
}
Пример #2
0
void *pluginsd_worker_thread(void *arg)
{
	struct plugind *cd = (struct plugind *)arg;
	char line[PLUGINSD_LINE_MAX + 1];

#ifdef DETACH_PLUGINS_FROM_NETDATA
	unsigned long long usec = 0, susec = 0;
	struct timeval last = {0, 0} , now = {0, 0};
#endif

	char *words[MAX_WORDS] = { NULL };
	uint32_t SET_HASH = simple_hash("SET");
	uint32_t BEGIN_HASH = simple_hash("BEGIN");
	uint32_t END_HASH = simple_hash("END");
	uint32_t FLUSH_HASH = simple_hash("FLUSH");
	uint32_t CHART_HASH = simple_hash("CHART");
	uint32_t DIMENSION_HASH = simple_hash("DIMENSION");
	uint32_t DISABLE_HASH = simple_hash("DISABLE");
#ifdef DETACH_PLUGINS_FROM_NETDATA
	uint32_t MYPID_HASH = simple_hash("MYPID");
	uint32_t STOPPING_WAKE_ME_UP_PLEASE_HASH = simple_hash("STOPPING_WAKE_ME_UP_PLEASE");
#endif

	while(likely(1)) {
		if(unlikely(netdata_exit)) break;

		FILE *fp = mypopen(cd->cmd, &cd->pid);
		if(unlikely(!fp)) {
			error("Cannot popen(\"%s\", \"r\").", cd->cmd);
			break;
		}

		info("PLUGINSD: '%s' running on pid %d", cd->fullfilename, cd->pid);

		RRDSET *st = NULL;
		unsigned long long count = 0;
		char *s;
		uint32_t hash;

		while(likely(fgets(line, PLUGINSD_LINE_MAX, fp) != NULL)) {
			if(unlikely(netdata_exit)) break;

			line[PLUGINSD_LINE_MAX] = '\0';

			// debug(D_PLUGINSD, "PLUGINSD: %s: %s", cd->filename, line);

			int w = pluginsd_split_words(line, words, MAX_WORDS);
			s = words[0];
			if(unlikely(!s || !*s || !w)) {
				// debug(D_PLUGINSD, "PLUGINSD: empty line");
				continue;
			}

			// debug(D_PLUGINSD, "PLUGINSD: words 0='%s' 1='%s' 2='%s' 3='%s' 4='%s' 5='%s' 6='%s' 7='%s' 8='%s' 9='%s'", words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]);

			hash = simple_hash(s);

			if(likely(hash == SET_HASH && !strcmp(s, "SET"))) {
				char *dimension = words[1];
				char *value = words[2];

				if(unlikely(!dimension || !*dimension)) {
					error("PLUGINSD: '%s' is requesting a SET on chart '%s', without a dimension. Disabling it.", cd->fullfilename, st->id);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				if(unlikely(!value || !*value)) value = NULL;

				if(unlikely(!st)) {
					error("PLUGINSD: '%s' is requesting a SET on dimension %s with value %s, without a BEGIN. Disabling it.", cd->fullfilename, dimension, value?value:"<nothing>");
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is setting dimension %s/%s to %s", cd->fullfilename, st->id, dimension, value?value:"<nothing>");

				if(value) rrddim_set(st, dimension, atoll(value));

				count++;
			}
			else if(likely(hash == BEGIN_HASH && !strcmp(s, "BEGIN"))) {
				char *id = words[1];
				char *microseconds_txt = words[2];

				if(unlikely(!id)) {
					error("PLUGINSD: '%s' is requesting a BEGIN without a chart id. Disabling it.", cd->fullfilename);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				st = rrdset_find(id);
				if(unlikely(!st)) {
					error("PLUGINSD: '%s' is requesting a BEGIN on chart '%s', which does not exist. Disabling it.", cd->fullfilename, id);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				if(likely(st->counter_done)) {
					unsigned long long microseconds = 0;
					if(microseconds_txt && *microseconds_txt) microseconds = strtoull(microseconds_txt, NULL, 10);
					if(microseconds) rrdset_next_usec(st, microseconds);
					else rrdset_next_plugins(st);
				}
			}
			else if(likely(hash == END_HASH && !strcmp(s, "END"))) {
				if(unlikely(!st)) {
					error("PLUGINSD: '%s' is requesting an END, without a BEGIN. Disabling it.", cd->fullfilename);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting a END on chart %s", cd->fullfilename, st->id);

				rrdset_done(st);
				st = NULL;
			}
			else if(likely(hash == FLUSH_HASH && !strcmp(s, "FLUSH"))) {
				debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting a FLUSH", cd->fullfilename);
				st = NULL;
			}
			else if(likely(hash == CHART_HASH && !strcmp(s, "CHART"))) {
				st = NULL;

				char *type = words[1];
				char *id = NULL;
				if(likely(type)) {
					id = strchr(type, '.');
					if(likely(id)) { *id = '\0'; id++; }
				}
				char *name = words[2];
				char *title = words[3];
				char *units = words[4];
				char *family = words[5];
				char *category = words[6];
				char *chart = words[7];
				char *priority_s = words[8];
				char *update_every_s = words[9];

				if(unlikely(!type || !*type || !id || !*id)) {
					error("PLUGINSD: '%s' is requesting a CHART, without a type.id. Disabling it.", cd->fullfilename);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				int priority = 1000;
				if(likely(priority_s)) priority = atoi(priority_s);

				int update_every = cd->update_every;
				if(likely(update_every_s)) update_every = atoi(update_every_s);
				if(unlikely(!update_every)) update_every = cd->update_every;

				int chart_type = RRDSET_TYPE_LINE;
				if(unlikely(chart)) chart_type = rrdset_type_id(chart);

				if(unlikely(!name || !*name)) name = NULL;
				if(unlikely(!family || !*family)) family = id;
				if(unlikely(!category || !*category)) category = type;

				st = rrdset_find_bytype(type, id);
				if(unlikely(!st)) {
					debug(D_PLUGINSD, "PLUGINSD: Creating chart type='%s', id='%s', name='%s', family='%s', category='%s', chart='%s', priority=%d, update_every=%d"
						, type, id
						, name?name:""
						, family?family:""
						, category?category:""
						, rrdset_type_name(chart_type)
						, priority
						, update_every
						);

					st = rrdset_create(type, id, name, family, title, units, priority, update_every, chart_type);
					cd->update_every = update_every;

					if(unlikely(strcmp(category, "none") == 0)) st->isdetail = 1;
				}
				else debug(D_PLUGINSD, "PLUGINSD: Chart '%s' already exists. Not adding it again.", st->id);
			}
			else if(likely(hash == DIMENSION_HASH && !strcmp(s, "DIMENSION"))) {
				char *id = words[1];
				char *name = words[2];
				char *algorithm = words[3];
				char *multiplier_s = words[4];
				char *divisor_s = words[5];
				char *options = words[6];

				if(unlikely(!id || !*id)) {
					error("PLUGINSD: '%s' is requesting a DIMENSION, without an id. Disabling it.", cd->fullfilename);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				if(unlikely(!st)) {
					error("PLUGINSD: '%s' is requesting a DIMENSION, without a CHART. Disabling it.", cd->fullfilename);
					cd->enabled = 0;
					killpid(cd->pid, SIGTERM);
					break;
				}

				long multiplier = 1;
				if(multiplier_s && *multiplier_s) multiplier = atol(multiplier_s);
				if(unlikely(!multiplier)) multiplier = 1;

				long divisor = 1;
				if(likely(divisor_s && *divisor_s)) divisor = atol(divisor_s);
				if(unlikely(!divisor)) divisor = 1;

				if(unlikely(!algorithm || !*algorithm)) algorithm = "absolute";

				if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: Creating dimension in chart %s, id='%s', name='%s', algorithm='%s', multiplier=%ld, divisor=%ld, hidden='%s'"
					, st->id
					, id
					, name?name:""
					, rrddim_algorithm_name(rrddim_algorithm_id(algorithm))
					, multiplier
					, divisor
					, options?options:""
					);

				RRDDIM *rd = rrddim_find(st, id);
				if(unlikely(!rd)) {
					rd = rrddim_add(st, id, name, multiplier, divisor, rrddim_algorithm_id(algorithm));
					rd->flags = 0x00000000;
					if(options && *options) {
						if(strstr(options, "hidden") != NULL) rd->flags |= RRDDIM_FLAG_HIDDEN;
						if(strstr(options, "noreset") != NULL) rd->flags |= RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS;
						if(strstr(options, "nooverflow") != NULL) rd->flags |= RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS;
					}
				}
				else if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: dimension %s/%s already exists. Not adding it again.", st->id, id);
			}
			else if(unlikely(hash == DISABLE_HASH && !strcmp(s, "DISABLE"))) {
				error("PLUGINSD: '%s' called DISABLE. Disabling it.", cd->fullfilename);
				cd->enabled = 0;
				killpid(cd->pid, SIGTERM);
				break;
			}
#ifdef DETACH_PLUGINS_FROM_NETDATA
			else if(likely(hash == MYPID_HASH && !strcmp(s, "MYPID"))) {
				char *pid_s = words[1];
				pid_t pid = atol(pid_s);

				if(likely(pid)) cd->pid = pid;
				debug(D_PLUGINSD, "PLUGINSD: %s is on pid %d", cd->id, cd->pid);
			}
			else if(likely(hash == STOPPING_WAKE_ME_UP_PLEASE_HASH && !strcmp(s, "STOPPING_WAKE_ME_UP_PLEASE"))) {
				error("PLUGINSD: '%s' (pid %d) called STOPPING_WAKE_ME_UP_PLEASE.", cd->fullfilename, cd->pid);

				gettimeofday(&now, NULL);
				if(unlikely(!usec && !susec)) {
					// our first run
					susec = cd->rrd_update_every * 1000000ULL;
				}
				else {
					// second+ run
					usec = usecdiff(&now, &last) - susec;
					error("PLUGINSD: %s last loop took %llu usec (worked for %llu, sleeped for %llu).\n", cd->fullfilename, usec + susec, usec, susec);
					if(unlikely(usec < (rrd_update_every * 1000000ULL / 2ULL))) susec = (rrd_update_every * 1000000ULL) - usec;
					else susec = rrd_update_every * 1000000ULL / 2ULL;
				}

				error("PLUGINSD: %s sleeping for %llu. Will kill with SIGCONT pid %d to wake it up.\n", cd->fullfilename, susec, cd->pid);
				usleep(susec);
				killpid(cd->pid, SIGCONT);
				bcopy(&now, &last, sizeof(struct timeval));
				break;
			}
#endif
			else {
				error("PLUGINSD: '%s' is sending command '%s' which is not known by netdata. Disabling it.", cd->fullfilename, s);
				cd->enabled = 0;
				killpid(cd->pid, SIGTERM);
				break;
			}
		}

		info("PLUGINSD: '%s' on pid %d stopped.", cd->fullfilename, cd->pid);

		// fgets() failed or loop broke
		int code = mypclose(fp, cd->pid);
		if(code == 1 || code == 127) {
			// 1 = DISABLE
			// 127 = cannot even run it
			error("PLUGINSD: '%s' (pid %d) exited with code %d. Disabling it.", cd->fullfilename, cd->pid, code);
			cd->enabled = 0;
		}

		if(netdata_exit) {
			cd->pid = 0;
			cd->enabled = 0;
			cd->obsolete = 1;
			return NULL;
		}

		if(unlikely(!count && cd->enabled)) {
			error("PLUGINSD: '%s' (pid %d) does not generate usefull output. Waiting a bit before starting it again.", cd->fullfilename, cd->pid);
			sleep((unsigned int) (cd->update_every * 10));
		}

		cd->pid = 0;
		if(likely(cd->enabled)) sleep((unsigned int) cd->update_every);
		else break;
	}

	cd->obsolete = 1;
	return NULL;
}
Пример #3
0
BOOL
command(int commandc)
{
	char	filename[PATHLEN + 1];	/* file path name */
	MOUSEEVENT *p;			/* mouse data */
	int	c, i;
	FILE	*file;
	HISTORY *curritem, *item;	/* command history */
	char	*s;

	switch (commandc) {

	case ctrl('C'):	/* toggle caseless mode */
		if (caseless == NO) {
			caseless = YES;
			putmsg2("Caseless mode is now ON");
		} else {
			caseless = NO;
			putmsg2("Caseless mode is now OFF");
		}
		egrepcaseless(caseless);	/* turn on/off -i flag */
		return (NO);

	case ctrl('R'):	/* rebuild the cross reference */
		if (isuptodate == YES) {
			putmsg("The -d option prevents rebuilding the "
			    "symbol database");
			return (NO);
		}
		exitcurses();
		freefilelist();		/* remake the source file list */
		makefilelist();
		rebuild();
		if (errorsfound == YES) {
			errorsfound = NO;
			askforreturn();
		}
		entercurses();
		putmsg("");		/* clear any previous message */
		totallines = 0;
		topline = nextline = 1;
		break;

	case ctrl('X'):	/* mouse selection */
		if ((p = getmouseevent()) == NULL) {
			return (NO);	/* unknown control sequence */
		}
		/* if the button number is a scrollbar tag */
		if (p->button == '0') {
			scrollbar(p);
			break;
		}
		/* ignore a sweep */
		if (p->x2 >= 0) {
			return (NO);
		}
		/* if this is a line selection */
		if (p->y1 < FLDLINE) {

			/* find the selected line */
			/* note: the selection is forced into range */
			for (i = disprefs - 1; i > 0; --i) {
				if (p->y1 >= displine[i]) {
					break;
				}
			}
			/* display it in the file with the editor */
			editref(i);
		} else {	/* this is an input field selection */
			field = mouseselection(p, FLDLINE, FIELDS);
			setfield();
			resetcmd();
			return (NO);
		}
		break;

	case '\t':	/* go to next input field */
	case '\n':
	case '\r':
	case ctrl('N'):
	case KEY_DOWN:
	case KEY_ENTER:
	case KEY_RIGHT:
		field = (field + 1) % FIELDS;
		setfield();
		resetcmd();
		return (NO);

	case ctrl('P'):	/* go to previous input field */
	case KEY_UP:
	case KEY_LEFT:
		field = (field + (FIELDS - 1)) % FIELDS;
		setfield();
		resetcmd();
		return (NO);
	case KEY_HOME:	/* go to first input field */
		field = 0;
		setfield();
		resetcmd();
		return (NO);

	case KEY_LL:	/* go to last input field */
		field = FIELDS - 1;
		setfield();
		resetcmd();
		return (NO);
	case ' ':	/* display next page */
	case '+':
	case ctrl('V'):
	case KEY_NPAGE:
		/* don't redisplay if there are no lines */
		if (totallines == 0) {
			return (NO);
		}
		/*
		 * note: seekline() is not used to move to the next
		 * page because display() leaves the file pointer at
		 * the next page to optimize paging forward
		 */
		break;

	case '-':	/* display previous page */
	case KEY_PPAGE:
		/* don't redisplay if there are no lines */
		if (totallines == 0) {
			return (NO);
		}
		i = topline;		/* save the current top line */
		nextline = topline;	/* go back to this page */

		/* if on first page but not at beginning, go to beginning */
		if (nextline > 1 && nextline <= mdisprefs) {
			nextline = 1;
		} else {	/* go back the maximum displayable lines */
			nextline -= mdisprefs;

			/* if this was the first page, go to the last page */
			if (nextline < 1) {
				nextline = totallines - mdisprefs + 1;
				if (nextline < 1) {
					nextline = 1;
				}
				/* old top is past last line */
				i = totallines + 1;
			}
		}
		/*
		 * move down til the bottom line is just before the
		 * previous top line
		 */
		c = nextline;
		for (;;) {
			seekline(nextline);
			display();
			if (i - bottomline <= 0) {
				break;
			}
			nextline = ++c;
		}
		return (NO);	/* display already up to date */

	case '>':	/* write or append the lines to a file */
		if (totallines == 0) {
			putmsg("There are no lines to write to a file");
		} else {	/* get the file name */
			(void) move(PRLINE, 0);
			(void) addstr("Write to file: ");
			s = "w";
			if ((c = mygetch()) == '>') {
				(void) move(PRLINE, 0);
				(void) addstr(appendprompt);
				c = '\0';
				s = "a";
			}
			if (c != '\r' && c != '\n' && c != KEY_ENTER &&
			    c != KEY_BREAK &&
			    getaline(newpat, COLS - sizeof (appendprompt), c,
			    NO) > 0) {
				shellpath(filename, sizeof (filename), newpat);
				if ((file = fopen(filename, s)) == NULL) {
					cannotopen(filename);
				} else {
					seekline(1);
					while ((c = getc(refsfound)) != EOF) {
						(void) putc(c, file);
					}
					seekline(topline);
					(void) fclose(file);
				}
			}
			clearprompt();
		}
		return (NO);	/* return to the previous field */

	case '<':	/* read lines from a file */
		(void) move(PRLINE, 0);
		(void) addstr(readprompt);
		if (getaline(newpat, COLS - sizeof (readprompt), '\0',
		    NO) > 0) {
			clearprompt();
			shellpath(filename, sizeof (filename), newpat);
			if (readrefs(filename) == NO) {
				putmsg2("Ignoring an empty file");
				return (NO);
			}
			return (YES);
		}
		clearprompt();
		return (NO);

	case '^':	/* pipe the lines through a shell command */
	case '|':	/* pipe the lines to a shell command */
		if (totallines == 0) {
			putmsg("There are no lines to pipe to a shell command");
			return (NO);
		}
		/* get the shell command */
		(void) move(PRLINE, 0);
		(void) addstr(pipeprompt);
		if (getaline(newpat,
		    COLS - sizeof (pipeprompt), '\0', NO) == 0) {
			clearprompt();
			return (NO);
		}
		/* if the ^ command, redirect output to a temp file */
		if (commandc == '^') {
			(void) strcat(strcat(newpat, " >"), temp2);
		}
		exitcurses();
		if ((file = mypopen(newpat, "w")) == NULL) {
			(void) fprintf(stderr,
			    "cscope: cannot open pipe to shell command: %s\n",
			    newpat);
		} else {
			seekline(1);
			while ((c = getc(refsfound)) != EOF) {
				(void) putc(c, file);
			}
			seekline(topline);
			(void) mypclose(file);
		}
		if (commandc == '^') {
			if (readrefs(temp2) == NO) {
				putmsg("Ignoring empty output of ^ command");
			}
		}
		askforreturn();
		entercurses();
		break;

	case ctrl('L'):	/* redraw screen */
	case KEY_CLEAR:
		(void) clearok(curscr, TRUE);
		(void) wrefresh(curscr);
		drawscrollbar(topline, bottomline, totallines);
		return (NO);

	case '!':	/* shell escape */
		(void) execute(shell, shell, (char *)NULL);
		seekline(topline);
		break;

	case '?':	/* help */
		(void) clear();
		help();
		(void) clear();
		seekline(topline);
		break;

	case ctrl('E'):	/* edit all lines */
		editall();
		break;

	case ctrl('A'):	/* repeat last pattern */
	case ctrl('Y'):	/* (old command) */
		if (*pattern != '\0') {
			(void) addstr(pattern);
			goto repeat;
		}
		break;

	case ctrl('B'):		/* cmd history back */
	case ctrl('F'):		/* cmd history fwd */
		curritem = currentcmd();
		item = (commandc == ctrl('F')) ? nextcmd() : prevcmd();
		clearmsg2();
		if (curritem == item) {
			/* inform user that we're at history end */
			putmsg2(
			    "End of input field and search pattern history");
		}
		if (item) {
			field = item->field;
			setfield();
			atfield();
			(void) addstr(item->text);
			(void) strcpy(pattern, item->text);
			switch (c = mygetch()) {
			case '\r':
			case '\n':
			case KEY_ENTER:
				goto repeat;
			default:
				ungetch(c);
				atfield();
				(void) clrtoeol(); /* clear current field */
				break;
			}
		}
		return (NO);

	case '\\':	/* next character is not a command */
		(void) addch('\\');	/* display the quote character */

		/* get a character from the terminal */
		if ((commandc = mygetch()) == EOF) {
			return (NO);	/* quit */
		}
		(void) addstr("\b \b");	/* erase the quote character */
		goto ispat;

	case '.':
		atfield();	/* move back to the input field */
		/* FALLTHROUGH */
	default:
		/* edit a selected line */
		if (isdigit(commandc) && commandc != '0' && !mouse) {
			if (returnrequired == NO) {
				editref(commandc - '1');
			} else {
				(void) move(PRLINE, 0);
				(void) addstr(selectionprompt);
				if (getaline(newpat,
				    COLS - sizeof (selectionprompt), commandc,
				    NO) > 0 &&
				    (i = atoi(newpat)) > 0) {
					editref(i - 1);
				}
				clearprompt();
			}
		} else if (isprint(commandc)) {
			/* this is the start of a pattern */
ispat:
			if (getaline(newpat, COLS - fldcolumn - 1, commandc,
			    caseless) > 0) {
					(void) strcpy(pattern, newpat);
					resetcmd();	/* reset history */
repeat:
				addcmd(field, pattern);	/* add to history */
				if (field == CHANGE) {
					/* prompt for the new text */
					(void) move(PRLINE, 0);
					(void) addstr(toprompt);
					(void) getaline(newpat,
					    COLS - sizeof (toprompt), '\0', NO);
				}
				/* search for the pattern */
				if (search() == YES) {
					switch (field) {
					case DEFINITION:
					case FILENAME:
						if (totallines > 1) {
							break;
						}
						topline = 1;
						editref(0);
						break;
					case CHANGE:
						return (changestring());
					}
				} else if (field == FILENAME &&
				    access(newpat, READ) == 0) {
					/* try to edit the file anyway */
					edit(newpat, "1");
				}
			} else {	/* no pattern--the input was erased */
				return (NO);
			}
		} else {	/* control character */
			return (NO);
		}
	}
	return (YES);
}