/* * ex_read -- :read [file] * :read [!cmd] * Read from a file or utility. * * !!! * Historical vi wouldn't undo a filter read, for no apparent reason. * * PUBLIC: int ex_read __P((SCR *, EXCMD *)); */ int ex_read(SCR *sp, EXCMD *cmdp) { enum { R_ARG, R_EXPANDARG, R_FILTER } which; struct stat sb; CHAR_T *arg; char *name; size_t nlen; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; db_recno_t nlines; size_t arglen; int argc, rval; char *p; char *np; gp = sp->gp; /* * 0 args: read the current pathname. * 1 args: check for "read !arg". */ switch (cmdp->argc) { case 0: which = R_ARG; break; case 1: arg = cmdp->argv[0]->bp; arglen = cmdp->argv[0]->len; if (*arg == '!') { ++arg; --arglen; which = R_FILTER; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } } else which = R_EXPANDARG; break; default: abort(); /* NOTREACHED */ } /* Load a temporary file if no file being edited. */ if (sp->ep == NULL) { if ((frp = file_add(sp, NULL)) == NULL) return (1); if (file_init(sp, frp, NULL, 0)) return (1); } switch (which) { case R_FILTER: /* * File name and bang expand the user's argument. If * we don't get an additional argument, it's illegal. */ argc = cmdp->argc; if (argv_exp1(sp, cmdp, arg, arglen, 1)) return (1); if (argc == cmdp->argc) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } argc = cmdp->argc - 1; /* Set the last bang command. */ exp = EXP(sp); if (exp->lastbcomm != NULL) free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, cmdp->argv[argc]->bp, cmdp->argv[argc]->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * Vi redisplayed the user's argument if it changed, ex * always displayed a !, plus the user's argument if it * changed. */ if (F_ISSET(sp, SC_VI)) { if (F_ISSET(cmdp, E_MODIFY)) (void)vs_update(sp, "!", cmdp->argv[argc]->bp); } else { if (F_ISSET(cmdp, E_MODIFY)) (void)ex_printf(sp, "!%s\n", cmdp->argv[argc]->bp); else (void)ex_puts(sp, "!\n"); (void)ex_fflush(sp); } /* * Historically, filter reads as the first ex command didn't * wait for the user. If SC_SCR_EXWROTE not already set, set * the don't-wait flag. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); /* * Switch into ex canonical mode. The reason to restore the * original terminal modes for read filters is so that users * can do things like ":r! cat /dev/tty". * * !!! * We do not output an extra <newline>, so that we don't touch * the screen on a normal read. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON_F); return (1); } /* * !!! * Historically, the read command doesn't switch to * the alternate X11 xterm screen, if doing a filter * read -- don't set SA_ALTERNATE. */ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } if (ex_filter(sp, cmdp, &cmdp->addr1, NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ)) return (1); /* The filter version of read set the autoprint flag. */ F_SET(cmdp, E_AUTOPRINT); /* * If in vi mode, move to the first nonblank. Might have * switched into ex mode, so saved the original SC_VI value. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } return (0); case R_ARG: name = sp->frp->name; break; case R_EXPANDARG: if (argv_exp2(sp, cmdp, arg, arglen)) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len + 1, name, nlen); /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((p = strdup(name)) != NULL) { free(sp->frp->name); sp->frp->name = p; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); name = sp->frp->name; } else { set_alt_name(sp, name); name = sp->alt_name; } break; default: ex_wemsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT); return (1); } break; } /* * !!! * Historically, vi did not permit reads from non-regular files, nor * did it distinguish between "read !" and "read!", so there was no * way to "force" it. We permit reading from named pipes too, since * they didn't exist when the original implementation of vi was done * and they seem a reasonable addition. */ if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { msgq_str(sp, M_SYSERR, name, "%s"); return (1); } if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) { (void)fclose(fp); msgq(sp, M_ERR, "145|Only regular files and named pipes may be read"); return (1); } /* Try and get a lock. */ if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL) msgq(sp, M_ERR, "146|%s: read lock was unavailable", name); rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0); /* * In vi, set the cursor to the first line read in, if anything read * in, otherwise, the address. (Historic vi set it to the line after * the address regardless, but since that line may not exist we don't * bother.) * * In ex, set the cursor to the last line read in, if anything read in, * otherwise, the address. */ if (F_ISSET(sp, SC_VI)) { sp->lno = cmdp->addr1.lno; if (nlines) ++sp->lno; } else sp->lno = cmdp->addr1.lno + nlines; return (rval); }
/* * exwr -- * The guts of the ex write commands. */ static int exwr(SCR *sp, EXCMD *cmdp, enum which cmd) { MARK rm; int flags; char *name; CHAR_T *p = NULL; size_t nlen; char *n; int rc; EX_PRIVATE *exp; NEEDFILE(sp, cmdp); /* All write commands can have an associated '!'. */ LF_INIT(FS_POSSIBLE); if (FL_ISSET(cmdp->iflags, E_C_FORCE)) LF_SET(FS_FORCE); /* Skip any leading whitespace. */ if (cmdp->argc != 0) for (p = cmdp->argv[0]->bp; *p != '\0' && cmdskip(*p); ++p); /* If "write !" it's a pipe to a utility. */ if (cmdp->argc != 0 && cmd == WRITE && *p == '!') { /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } /* Expand the argument. */ for (++p; *p && cmdskip(*p); ++p); if (*p == '\0') { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } if (argv_exp1(sp, cmdp, p, STRLEN(p), 1)) return (1); /* Set the last bang command */ exp = EXP(sp); free(exp->lastbcomm); exp->lastbcomm = v_wstrdup(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len); /* * Historically, vi waited after a write filter even if there * wasn't any output from the command. People complained when * nvi waited only if there was output, wanting the visual cue * that the program hadn't written anything. */ F_SET(sp, SC_EX_WAIT_YES); /* * !!! * Ignore the return cursor position, the cursor doesn't * move. */ if (ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE)) return (1); /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); return (0); } /* Set the FS_ALL flag if we're writing the entire file. */ if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1)) LF_SET(FS_ALL); /* If "write >>" it's an append to a file. */ if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') { LF_SET(FS_APPEND); /* Skip ">>" and whitespace. */ for (p += 2; *p && cmdskip(*p); ++p); } /* If no other arguments, just write the file back. */ if (cmdp->argc == 0 || *p == '\0') return (file_write(sp, &cmdp->addr1, &cmdp->addr2, NULL, flags)); /* Build an argv so we get an argument count and file expansion. */ if (argv_exp2(sp, cmdp, p, STRLEN(p))) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len+1, n, nlen); name = v_strdup(sp, n, nlen - 1); /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((n = v_strdup(sp, name, nlen - 1)) != NULL) { free(sp->frp->name); sp->frp->name = n; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. * * !!! * If we're writing the whole file, FR_NAMECHANGE * will be cleared by the write routine -- this is * historic practice. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } else set_alt_name(sp, name); break; default: INT2CHAR(sp, p, STRLEN(p) + 1, n, nlen); ex_emsg(sp, n, EXM_FILECOUNT); return (1); } rc = file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags); free(name); return rc; }
/* * cscope_add -- * The cscope add command. */ static int cscope_add(SCR *sp, EXCMD *cmdp, const CHAR_T *dname) { struct stat sb; EX_PRIVATE *exp; CSC *csc; size_t len; int cur_argc; const char *dbname; char path[MAXPATHLEN]; const char *np; char *npp; size_t nlen; exp = EXP(sp); /* * 0 additional args: usage. * 1 additional args: matched a file. * >1 additional args: object, too many args. */ cur_argc = cmdp->argc; if (argv_exp2(sp, cmdp, dname, STRLEN(dname))) { return (1); } if (cmdp->argc == cur_argc) { (void)csc_help(sp, "add"); return (1); } if (cmdp->argc == cur_argc + 1) dname = cmdp->argv[cur_argc]->bp; else { INT2CHAR(sp, dname, STRLEN(dname)+1, np, nlen); ex_emsg(sp, np, EXM_FILECOUNT); return (1); } INT2CHAR(sp, dname, STRLEN(dname)+1, np, nlen); /* * The user can specify a specific file (so they can have multiple * Cscope databases in a single directory) or a directory. If the * file doesn't exist, we're done. If it's a directory, append the * standard database file name and try again. Store the directory * name regardless so that we can use it as a base for searches. */ if (stat(np, &sb)) { msgq(sp, M_SYSERR, "%s", np); return (1); } if (S_ISDIR(sb.st_mode)) { (void)snprintf(path, sizeof(path), "%s/%s", np, CSCOPE_DBFILE); if (stat(path, &sb)) { msgq(sp, M_SYSERR, "%s", path); return (1); } dbname = CSCOPE_DBFILE; } else if ((npp = strrchr(np, '/')) != NULL) { *npp = '\0'; dbname = npp + 1; } else { dbname = np; np = "."; } /* Allocate a cscope connection structure and initialize its fields. */ len = strlen(np); CALLOC_RET(sp, csc, CSC *, 1, sizeof(CSC) + len); csc->dname = csc->buf; csc->dlen = len; memcpy(csc->dname, np, len); csc->mtime = sb.st_mtime; /* Get the search paths for the cscope. */ if (get_paths(sp, csc)) goto err; /* Start the cscope process. */ if (run_cscope(sp, csc, dbname)) goto err; /* * Add the cscope connection to the screen's list. From now on, * on error, we have to call terminate, which expects the csc to * be on the chain. */ LIST_INSERT_HEAD(&exp->cscq, csc, q); /* Read the initial prompt from the cscope to make sure it's okay. */ return read_prompt(sp, csc); err: free(csc); return (1); }
/* * file_backup -- * Backup the about-to-be-written file. * * XXX * We do the backup by copying the entire file. It would be nice to do * a rename instead, but: (1) both files may not fit and we want to fail * before doing the rename; (2) the backup file may not be on the same * disk partition as the file being written; (3) there may be optional * file information (MACs, DACs, whatever) that we won't get right if we * recreate the file. So, let's not risk it. */ static int file_backup(SCR *sp, char *name, char *bname) { struct dirent *dp; struct stat sb; DIR *dirp; EXCMD cmd; off_t off; size_t blen; int flags, maxnum, nr, num, nw, rfd, wfd, version; char *bp, *estr, *p, *pct, *slash, *t, *wfname, buf[8192]; CHAR_T *wp; size_t wlen; size_t nlen; char *d = NULL; rfd = wfd = -1; bp = estr = wfname = NULL; /* * Open the current file for reading. Do this first, so that * we don't exec a shell before the most likely failure point. * If it doesn't exist, it's okay, there's just nothing to back * up. */ errno = 0; if ((rfd = open(name, O_RDONLY, 0)) < 0) { if (errno == ENOENT) return (0); estr = name; goto err; } /* * If the name starts with an 'N' character, add a version number * to the name. Strip the leading N from the string passed to the * expansion routines, for no particular reason. It would be nice * to permit users to put the version number anywhere in the backup * name, but there isn't a special character that we can use in the * name, and giving a new character a special meaning leads to ugly * hacks both here and in the supporting ex routines. * * Shell and file name expand the option's value. */ ex_cinit(sp, &cmd, 0, 0, 0, 0, 0); if (bname[0] == 'N') { version = 1; ++bname; } else version = 0; CHAR2INT(sp, bname, strlen(bname) + 1, wp, wlen); if (argv_exp2(sp, &cmd, wp, wlen - 1)) return (1); /* * 0 args: impossible. * 1 args: use it. * >1 args: object, too many args. */ if (cmd.argc != 1) { msgq_str(sp, M_ERR, bname, "258|%s expanded into too many file names"); (void)close(rfd); return (1); } /* * If appending a version number, read through the directory, looking * for file names that match the name followed by a number. Make all * of the other % characters in name literal, so the user doesn't get * surprised and sscanf doesn't drop core indirecting through pointers * that don't exist. If any such files are found, increment its number * by one. */ if (version) { GET_SPACE_GOTOC(sp, bp, blen, cmd.argv[0]->len * 2 + 50); INT2SYS(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, p, nlen); d = strdup(p); p = d; for (t = bp, slash = NULL; p[0] != '\0'; *t++ = *p++) if (p[0] == '%') { if (p[1] != '%') *t++ = '%'; } else if (p[0] == '/') slash = t; pct = t; *t++ = '%'; *t++ = 'd'; *t = '\0'; if (slash == NULL) { dirp = opendir("."); p = bp; } else { *slash = '\0'; dirp = opendir(bp); *slash = '/'; p = slash + 1; } if (dirp == NULL) { INT2SYS(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, estr, nlen); goto err; } for (maxnum = 0; (dp = readdir(dirp)) != NULL;) if (sscanf(dp->d_name, p, &num) == 1 && num > maxnum) maxnum = num; (void)closedir(dirp); /* Format the backup file name. */ (void)snprintf(pct, blen - (pct - bp), "%d", maxnum + 1); wfname = bp; } else { bp = NULL; INT2SYS(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, wfname, nlen); } /* Open the backup file, avoiding lurkers. */ if (stat(wfname, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { msgq_str(sp, M_ERR, bname, "259|%s: not a regular file"); goto err; } if (sb.st_uid != getuid()) { msgq_str(sp, M_ERR, bname, "260|%s: not owned by you"); goto err; } if (sb.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) { msgq_str(sp, M_ERR, bname, "261|%s: accessible by a user other than the owner"); goto err; } flags = O_TRUNC; } else flags = O_CREAT | O_EXCL; if ((wfd = open(wfname, flags | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) { estr = bname; goto err; } /* Copy the file's current contents to its backup value. */ while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr != 0; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) { estr = wfname; goto err; } if (nr < 0) { estr = name; goto err; } if (close(rfd)) { estr = name; goto err; } if (close(wfd)) { estr = wfname; goto err; } if (bp != NULL) FREE_SPACE(sp, bp, blen); return (0); alloc_err: err: if (rfd != -1) (void)close(rfd); if (wfd != -1) { (void)unlink(wfname); (void)close(wfd); } if (estr) msgq_str(sp, M_SYSERR, estr, "%s"); if (d != NULL) free(d); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (1); }