/* vi:ts=4:sw=4 * * VIM - Vi IMproved * * Code Contributions By: Bram Moolenaar mool@oce.nl * Tim Thompson twitch!tjt * Tony Andrews onecom!wldrdg!tony * G. R. (Fred) Walter watmath!watcgl!grwalter */ /* * fileio.c: read from and write to a file */ /* * special feature of this version: NUL characters in the file are * replaced by newline characters in memory. This allows us to edit * binary files! */ #ifdef MSDOS # include #endif #include "vim.h" #include "globals.h" #include "proto.h" #include "param.h" #include "fcntl.h" #ifdef JP #include "jp.h" extern num_jis0201r(); #endif #ifdef LATTICE # include /* for Lock() and UnLock() */ #endif static int noendofline = FALSE; /* Set to TRUE when last line has no EOL in binary mode */ #define BUFSIZE 4096 /* size of normal write buffer */ #define SBUFSIZE 256 /* size of emergency write buffer */ static int write_buf __ARGS((int, char *, int)); static void do_mlines __ARGS((void)); void filemess(name, s) char *name; char *s; { smsg("\"%s\" %s", ((name == NULL) ? "" : name), s); } /* * Read lines from file 'fname' into the buffer after line 'from'. * * 1. We allocate blocks with m_blockalloc, as big as possible. * 2. Each block is filled with characters from the file with a single read(). * 3. The lines are inserted in the buffer with appendline(). * * (caller must check that fname != NULL) */ int readfile(fname, sfname, from, newfile) char *fname; char *sfname; linenr_t from; int newfile; { #ifdef UNIX int fd = -1; #else int fd; #endif register u_char c; register linenr_t lnum = from; register u_char *ptr = NULL; /* pointer into read buffer */ register u_char *buffer = NULL; /* read buffer */ #ifdef JP long size; #else register long size; #endif register u_char *p; long filesize; #define UNKNOWN 0x0fffffff /* file size is unknown */ linenr_t linecnt = line_count; int incomplete = FALSE; /* was the last line incomplete? */ int error = 0; /* read errors encountered */ long linerest = 0; /* remaining characters in line */ long filerest; /* remaining characters in file */ int firstpart = TRUE; /* reading first part */ #ifdef UNIX int perm; #endif int textmode = p_tx; /* accept CR-LF for line break */ #ifdef MDOMAIN int isurl, total, count; #endif if (sfname == NULL) sfname = fname; /* * Use the short filename whenever possible. * Avoids problems with networks and when directory names are changed. */ if (!did_cd) fname = sfname; if (bufempty()) /* special case: buffer has no lines */ linecnt = 0; #ifdef MDOMAIN /* check whether fname is URL or not */ count = 0; isurl = is_url(fname); if (!isurl) { #endif #ifdef UNIX /* * On Unix it is possible to read a directory, so we have to * check for it before the open(). */ perm = getperm(fname); #ifdef _POSIX_VERSION if (perm >= 0 && !S_ISREG(perm)) /* not a regular file */ #else if (perm >= 0 && (perm & S_IFMT) != S_IFREG) /* not a regular file */ #endif { #ifdef _POSIX_VERSION if (S_ISDIR(perm)) #else if ((perm & S_IFMT) == S_IFDIR) #endif filemess(fname, "is a directory"); else filemess(fname, "is not a file"); #ifdef JP JP_FCODE = JP_NEW; #endif return TRUE; } #endif # ifdef MDOMAIN } else perm = 0; # endif #ifdef MDOMAIN if (isurl) { p_ro = FALSE; if ((fd = url_open(fname, O_RDONLY, &total)) == -1) { filemess(fname, " not read."); #ifdef JP JP_FCODE = JP_NEW; #endif return TRUE; } } else #endif if ( #ifdef UNIX !(perm & 0200) || /* root's way to check RO */ #endif (fd = open(fname, O_RDWR)) == -1) /* cannot open r/w */ { if ((fd = open(fname, O_RDONLY)) == -1) /* cannot open at all */ { #ifdef MSDOS /* * The screen may be messed up by the "insert disk * in drive b: and hit return" message */ updateScreen(CLEAR); #endif #ifndef UNIX /* * On MSDOS and Amiga we can't open a directory, check here. */ if (isdir(fname) > 0) { filemess(fname, "is a directory"); p_ro = TRUE; } else #endif if (newfile) { filemess(fname, "[New File]"); if (!readonlymode) p_ro = FALSE; } #ifdef JP JP_FCODE = JP_NEW; #endif return TRUE; } if (newfile) /* set file readonly */ p_ro = TRUE; } else if (newfile && !readonlymode) /* set file not readonly */ p_ro = FALSE; if (newfile) noendofline = FALSE; #ifdef MDOMAIN if (isurl) filesize = total; else { #endif if ((filesize = lseek(fd, 0L, 2)) < 0) /* get length of file */ filesize = UNKNOWN; lseek(fd, 0L, 0); #ifdef MDOMAIN } #endif #ifdef JP reset_jcount(); /* prepair convert kanji code */ #endif filemess(fname, ""); /* show that we are busy */ for (filerest = filesize; !error && !got_int && filerest != 0; breakcheck()) { /* * We allocate as much space for the file as we can get, plus * space for the old line, one NUL in front and one NUL at the tail. * The amount is limited by the fact that read() only can read * upto max_unsigned characters (and other things). * If we don't know the file size, just get one Kbyte. */ if (filesize >= UNKNOWN) size = 1024; #if defined(MSDOS) && !defined(WIN32) else if (filerest > 0xff00L) size = 0xff00L; #endif else if (filerest < 10) size = 10; else size = filerest; for ( ; size >= 10; size /= 2) { if ((buffer = (u_char *)m_blockalloc((u_long)(size + linerest + 4), FALSE)) != NULL) break; } if (buffer == NULL) { emsg(e_outofmem); error = 1; break; } buffer[0] = NUL; /* make sure there is a NUL in front of the first line */ ++buffer; if (linerest) /* copy characters from the previous buffer */ { ptr -= linerest; memmove((char *)buffer, (char *)ptr, linerest); memset((char *)ptr, 1, linerest); /* fill with non-NULs */ ptr[linerest - 1] = NUL; /* add a NUL on the end */ free_line((char *)ptr); /* free the space we don't use */ } ptr = buffer + linerest; if ((size = (unsigned)read(fd, (char *)ptr, (size_t)size)) <= 0) { error = 2; break; } if (filesize >= UNKNOWN) /* if we don't know the file size */ filesize += size; /* .. count the number of characters */ else /* .. otherwise */ filerest -= size; /* .. compute the remaining length */ /* * when reading the first part of a file: guess EOL type */ if (firstpart && p_ta) { for (p = ptr; p < ptr + size; ++p) if (*p == NL) { if (p > ptr && p[-1] == CR) /* found CR-NL */ textmode = TRUE; else /* found a single NL */ textmode = FALSE; /* if editing a new file: may set p_tx */ if (newfile && p_tx != textmode) { p_tx = textmode; paramchanged("tx"); } break; } } --ptr; /* * This loop is executed once for every character read. * Keep it fast! */ while (++ptr, --size >= 0) { if ((c = *ptr) != NUL && c != NL) /* catch most common case */ continue; if (c == NUL) *ptr = NL; /* NULs are replaced by newlines! */ else { *ptr = NUL; /* end of line */ if (textmode && ptr[-1] == CR) /* remove CR */ ptr[-1] = NUL; #ifdef JP if (JP_READ != JP_NONE) { char tmp[IOSIZE]; int n, l, min; int tmp_jse; tmp_jse = p_jse; p_jse = TRUE; n = kanjiconvsfrom(buffer, l = ptr - buffer, tmp, IOSIZE, NULL, JP_READ, NULL); p_jse = tmp_jse; if (n < 0) { emsg2("Line %d: Too many JISX0201 right char./line.", lnum); error = 1; break; } min = n < l ? n : l; memmove(buffer, tmp, min); buffer[min] = NUL; if (!appendline(lnum, (char *)buffer)) { error = 1; break; } if (n > l) { int lorg; char *lptr; lorg = Curpos.lnum; Curpos.lnum = lnum + 1; if (!canincrease(n - l)) { error = 1; break; } lptr = nr2ptr(Curpos.lnum); memmove(lptr + l, tmp + l, n - l); lptr[n] = NUL; Curpos.lnum = lorg; } } else #endif if (!appendline(lnum, (char *)buffer)) { error = 1; break; } ++lnum; buffer = ptr + 1; } } linerest = ptr - buffer; firstpart = FALSE; } if (lnum != from && !newfile) /* added at least one line */ CHANGED; if (error != 1 && linerest != 0) { /* * If we get EOF in the middle of a line, note the fact and * complete the line ourselves. */ incomplete = TRUE; if (newfile && p_bin) /* remember for when writing */ noendofline = TRUE; *ptr = NUL; if (!appendline(lnum, (char *)buffer)) error = 1; else if (!newfile) CHANGED; } if (error == 2 && filesize >= UNKNOWN) /* no error, just EOF encountered */ { filesize -= UNKNOWN; error = 0; } close(fd); #ifdef MSDOS /* the screen may be messed up by the "insert disk in drive b: and hit return" message */ updateScreen(CLEAR); #endif #ifdef JP /* guess kanji code */ { char jcode; jcode = judge_jcode(JP_READ); if (newfile && JP_READ != JP_NONE) if ((JP_FCODE = jcode) == JP_NONE) JP_FCODE = JP_NEW; if (JP_READ == JP_ANY && jcode == JP_SJIS) { p_ro = TRUE; emsg("SJIS file: use ',' for the 1st char. of jmask"); } if (JP_READ == JP_SANY && jcode == JP_EUC) { p_ro = TRUE; emsg("EUC file: use '.' for the 1st char. of jmask"); } if (num_jis0201r()) { p_ro = TRUE; emsg("Hankaku Kana ---> Zenkaku Katakana"); } } #endif #if defined(JPFEP) || defined(FEPCTRL) if (p_ja) { p_ji = guess_jiauto(p_ji, p_ja); #ifdef JPFEP switch(*p_ji) { case 'j': KanjiInput = TRUE; goto setmode; case 'a': KanjiInput = FALSE; setmode: fep_mode_switch(KanjiInput); } #else /* if FEPCTRL */ if (p_fc) switch(*p_ji) { case 'j': fep_force_on(); break; case 'a': fep_force_off(); break; } fep_off(); #endif } #endif if (got_int) { filemess(fname, e_interr); return FALSE; /* an interrupt isn't really an error */ } linecnt = line_count - linecnt; smsg( #ifdef JP # ifdef JPFEP "\"%s\" %s%s%s%s%ld line%s, %ld character%s [%c%c]", # else "\"%s\" %s%s%s%s%ld line%s, %ld character%s [%c]", # endif #else "\"%s\" %s%s%s%s%ld line%s, %ld character%s", #endif fname, p_ro ? "[readonly] " : "", incomplete ? "[Incomplete last line] " : "", error ? "[READ ERRORS] " : "", #ifdef MSDOS textmode ? "" : "[notextmode] ", #else textmode ? "[textmode] " : "", #endif (long)linecnt, plural((long)linecnt), filesize, plural(filesize) #ifdef JP , JP_FCODE #endif #ifdef JPFEP , *p_ji #endif ); if (error && newfile) /* with errors we should not write the file */ { p_ro = TRUE; paramchanged("ro"); } u_clearline(); /* cannot use "U" command after adding lines */ if (newfile) /* edit a new file: read mode from lines */ do_mlines(); if (from < line_count) { Curpos.lnum = from + 1; /* put cursor at first new line */ Curpos.col = 0; } return FALSE; } /* * writeit - write to file 'fname' lines 'start' through 'end' * * We do our own buffering here because fwrite() is so slow. * * If forceit is true, we don't care for errors when attempting backups (jw). * In case of an error everything possible is done to restore the original file. * But when forceit is TRUE, we risk loosing it. * When whole is TRUE and start == 1 and end == line_count, reset Changed. */ int writeit(fname, sfname, start, end, append, forceit, whole) char *fname; char *sfname; linenr_t start, end; int append; int forceit; int whole; { int fd; char *backup = NULL; register char *s; register u_char *ptr; register u_char c; register int len; register linenr_t lnum; long nchars; char *errmsg = NULL; char *buffer; char smallbuf[SBUFSIZE]; int bufsize; long perm = -1; /* file permissions */ int retval = TRUE; int newfile = FALSE; /* TRUE if file does not exist yet */ #ifdef UNIX struct stat old; int made_writable = FALSE; /* 'w' bit has been set */ #endif #ifdef AMIGA BPTR flock; #endif #ifdef MDOMAIN int total; #endif if (fname == NULL || *fname == NUL) /* safety check */ return FALSE; if (sfname == NULL) sfname = fname; /* * Use the short filename whenever possible. * Avoids problems with networks and when directory names are changed. */ if (!did_cd) fname = sfname; /* * Disallow writing from .exrc and .vimrc in current directory for * security reasons. */ if (secure) { secure = 2; emsg(e_curdir); return FALSE; } if (exiting) settmode(0); /* when exiting allow typahead now */ filemess(fname, ""); /* show that we are busy */ buffer = alloc(BUFSIZE); if (buffer == NULL) /* can't allocate big buffer, use small one */ { buffer = smallbuf; bufsize = SBUFSIZE; } else bufsize = BUFSIZE; #ifdef UNIX /* get information about original file (if there is one) */ old.st_dev = old.st_ino = 0; #ifdef MDOMAIN if (is_url(fname)) newfile=TRUE; else #endif if (stat(fname, &old)) newfile = TRUE; else { #ifdef _POSIX_VERSION if (!S_ISREG(old.st_mode)) /* not a file */ #else if ((old.st_mode & S_IFMT) != S_IFREG) /* not a file */ #endif { #ifdef _POSIX_VERSION if (S_ISDIR(old.st_mode)) #else if ((old.st_mode & S_IFMT) == S_IFDIR) #endif errmsg = "is a directory"; else errmsg = "is not a file"; goto fail; } perm = old.st_mode; } /* * If we are not appending, the file exists, and the 'writebackup' or * 'backup' option is set, try to make a backup copy of the file. */ if (!append && perm >= 0 && (p_wb || p_bk) && (fd = open(fname, O_RDONLY)) >= 0) { int bfd, buflen; char buf[BUFSIZE + 1], *wp; int some_error = FALSE; struct stat new; new.st_dev = new.st_ino = 0; /* * Unix semantics has it, that we may have a writable file, * that cannot be recreated with a simple open(..., O_CREAT, ) e.g: * - the directory is not writable, * - the file may be a symbolic link, * - the file may belong to another user/group, etc. * * For these reasons, the existing writable file must be truncated and * reused. Creation of a backup COPY will be attempted. */ if ((backup = modname(fname, ".bak")) == NULL) { some_error = TRUE; goto nobackup; } if (!stat(backup, &new) && new.st_dev == old.st_dev && new.st_ino == old.st_ino) { /* * may happen when modname gave the same file back. * E.g. silly link, or filename-length reached. * If we don't check here, we either ruin the file when * copying or erase it after writing. jw. */ errmsg = "Invalid backup file (use ! to override)"; free(backup); backup = NULL; /* there is no backup file to delete */ goto nobackup; } remove(backup); /* remove old backup, if present */ if ((bfd = open(backup, O_WRONLY | O_CREAT, 0666)) < 0) { /* * oops, no write/create permission here? * try again in p_bdir directory. */ for (wp = fname + strlen(fname); wp >= fname; wp--) if (*wp == '/') break; ++wp; sprintf(buf, "%s/%s", p_bdir, wp); free(backup); if ((backup = modname(buf, ".bak")) == NULL) { some_error = TRUE; goto nobackup; } if (!stat(backup, &new) && new.st_dev == old.st_dev && new.st_ino == old.st_ino) { errmsg = "Invalid backup file (use ! to override)"; free(backup); backup = NULL; /* there is no backup file to delete */ goto nobackup; } remove(backup); if ((bfd = open(backup, O_WRONLY | O_CREAT, 0666)) < 0) { free(backup); backup = NULL; /* there is no backup file to delete */ errmsg = "Can't make backup file (use ! to override)"; goto nobackup; } } /* set file protection same as original file, but strip s-bit */ setperm(backup, perm & 0777); /* copy the file. */ while ((buflen = read(fd, buf, BUFSIZE)) > 0) { if (write_buf(bfd, buf, buflen) == -1) { errmsg = "Can't write to backup file (use ! to override)"; goto writeerr; } } writeerr: close(bfd); if (buflen < 0) errmsg = "Can't read file for backup (use ! to override)"; nobackup: close(fd); /* ignore errors when forceit is TRUE */ if ((some_error || errmsg) && !forceit) { retval = FALSE; goto fail; } errmsg = NULL; } /* if forceit and the file was read-only: make it writable */ if (forceit && (old.st_uid == getuid()) && perm >= 0 && !(perm & 0200)) { perm |= 0200; setperm(fname, perm); made_writable = TRUE; /* if we are writing to the current file, readonly makes no sense */ if (fname == Filename || fname == sFilename) p_ro = FALSE; } #else /* UNIX */ /* * If we are not appending, the file exists, and the 'writebackup' or * 'backup' option is set, make a backup. * Do not make any backup, if "writebackup" and "backup" are * both switched off. This helps when editing large files on * almost-full disks. (jw) */ perm = getperm(fname); if (perm < 0) newfile = TRUE; else if (isdir(fname) > 0) { errmsg = "is a directory"; goto fail; } if (!append && perm >= 0 && (p_wb || p_bk)) { /* * Form the backup file name - change path/fo.o.h to path/fo.o.h.bak */ backup = modname(fname, ".bak"); if (backup == NULL) { if (!forceit) goto fail; } else { /* * Delete any existing backup and move the current version to the backup. * For safety, we don't remove the backup until the write has finished * successfully. And if the 'backup' option is set, leave it around. */ #ifdef AMIGA /* * With MSDOS-compatible filesystems (crossdos, messydos) it is * possible that the name of the backup file is the same as the * original file. To avoid the chance of accidently deleting the * original file (horror!) we lock it during the remove. * This should not happen with ":w", because startscript() should * detect this problem and set thisfile_sn, causing modname to * return a correct ".bak" filename. This problem does exist with * ":w filename", but then the original file will be somewhere else * so the backup isn't really important. If autoscripting is off * the rename may fail. */ flock = Lock((UBYTE *)fname, (long)ACCESS_READ); #endif remove(backup); #ifdef AMIGA if (flock) UnLock(flock); #endif len = rename(fname, backup); if (len != 0) { if (forceit) { free(backup); /* don't do the rename below */ backup = NULL; } else { errmsg = "Can't make backup file (use ! to override)"; goto fail; } } } } #endif /* UNIX */ /* * We may try to open the file twice: If we can't write to the * file and forceit is TRUE we delete the existing file and try to create * a new one. If this still fails we may have lost the original file! * (this may happen when the user reached his quotum for number of files). * Appending will fail if the file does not exist and forceit is FALSE. */ #ifdef MDOMAIN if (is_url(fname)) { if ((fd = url_open(fname, O_WRONLY, &total)) == -1) goto fail; } else #endif while ((fd = open(fname, O_WRONLY | (append ? (forceit ? (O_APPEND | O_CREAT) : O_APPEND) : (O_CREAT | O_TRUNC)), 0666)) < 0) { /* * A forced write will try to create a new file if the old one is * still readonly. This may also happen when the directory is * read-only. In that case the remove() will fail. */ if (!errmsg) { errmsg = "Can't open file for writing"; if (forceit) { #ifdef UNIX /* we write to the file, thus it should be marked writable after all */ perm |= 0200; made_writable = TRUE; if (old.st_uid != getuid() || old.st_gid != getgid()) perm &= 0777; #endif /* UNIX */ if (!append) /* don't remove when appending */ remove(fname); continue; } } /* * If we failed to open the file, we don't need a backup. Throw it away. * If we moved or removed the original file try to put the backup in its place. */ if (backup) { #ifdef UNIX struct stat st; /* * There is a small chance that we removed the original, try * to move the copy in its place. * This won't work if the backup is in another file system! * In that case we leave the copy around. */ if (stat(fname, &st) < 0) /* file does not exist */ rename(backup, fname); /* put the copy in its place */ if (stat(fname, &st) >= 0) /* original file does exist */ remove(backup); /* throw away the copy */ #else rename(backup, fname); /* try to put the original file back */ #endif } goto fail; } errmsg = NULL; if (end > line_count) end = line_count; len = 0; s = buffer; nchars = 0; for (lnum = start; lnum <= end; ++lnum) { #ifdef JP char *tmpptr, *orgptr; orgptr = nr2ptr(lnum); if (JP_FCODE == JP_NONE) tmpptr = orgptr; else tmpptr = kanjiconvsto(orgptr, JP_FCODE); ptr = (u_char *)tmpptr - 1; #else /* JP */ ptr = (u_char *)nr2ptr(lnum) - 1; #endif /* JP */ /* * The next while loop is done once for each character written. * Keep it fast! */ while ((c = *++ptr) != NUL) { if (c == NL) *s = NUL; /* replace newlines with NULs */ else *s = c; ++s; if (++len != bufsize) continue; if (write_buf(fd, buffer, bufsize) == -1) { end = 0; /* write error: break loop */ break; } nchars += bufsize; s = buffer; len = 0; } /* write failed or last line has no EOL: stop here */ if (end == 0 || (p_bin && lnum == line_count && noendofline)) break; if (p_tx) /* write CR-NL */ { *s = CR; ++s; if (++len == bufsize) { if (write_buf(fd, buffer, bufsize) == -1) { end = 0; /* write error: break loop */ break; } nchars += bufsize; s = buffer; len = 0; } } #ifdef JP if (tmpptr != orgptr) free_line(tmpptr); #endif *s = NL; ++s; if (++len == bufsize && end) { if (write_buf(fd, buffer, bufsize) == -1) { end = 0; /* write error: break loop */ break; } nchars += bufsize; s = buffer; len = 0; } } if (len && end) { if (write_buf(fd, buffer, len) == -1) end = 0; /* write error */ nchars += len; } if (close(fd) != 0) { errmsg = "Close failed"; goto fail; } #ifdef UNIX if (made_writable) perm &= ~0200; /* reset 'w' bit for security reasons */ #endif if (perm >= 0) setperm(fname, perm); /* set permissions of new file same as old file */ if (end == 0) { errmsg = "write error (file system full?)"; goto fail; } #ifdef MSDOS /* the screen may be messed up by the "insert disk in drive b: and hit return" message */ if (!exiting) updateScreen(CLEAR); #endif lnum -= start; /* compute number of written lines */ smsg("\"%s\"%s%s %ld line%s, %ld character%s", fname, newfile ? " [New File]" : " ", #ifdef MSDOS p_tx ? "" : "[notextmode]", #else p_tx ? "[textmode]" : "", #endif (long)lnum, plural((long)lnum), nchars, plural(nchars)); if (whole && start == 1 && end == line_count) /* when written everything */ { UNCHANGED; NotEdited = FALSE; startscript(); /* re-start auto script file */ } /* * Remove the backup unless 'backup' option is set */ if (!p_bk && backup != NULL && remove(backup) != 0) emsg("Can't delete backup file"); goto nofail; fail: #ifdef MSDOS /* the screen may be messed up by the "insert disk in drive b: and hit return" message */ updateScreen(CLEAR); #endif nofail: free((char *) backup); free(buffer); if (errmsg != NULL) { filemess(fname, errmsg); retval = FALSE; } return retval; } /* * write_buf: call write() to write a buffer */ static int write_buf(fd, buf, len) int fd; char *buf; int len; { int wlen; while (len) { wlen = write(fd, buf, (size_t)len); if (wlen <= 0) /* error! */ return -1; len -= wlen; buf += wlen; } return 0; } /* * do_mlines() - process mode lines for the current file * * Returns immediately if the "ml" parameter isn't set. */ static void chk_mline __ARGS((linenr_t)); static void do_mlines() { linenr_t lnum; int nmlines; if (!p_ml || (nmlines = (int)p_mls) == 0) return; for (lnum = 1; lnum <= line_count && lnum <= nmlines; ++lnum) chk_mline(lnum); for (lnum = line_count; lnum > 0 && lnum > nmlines && lnum > line_count - nmlines; --lnum) chk_mline(lnum); } /* * chk_mline() - check a single line for a mode string */ static void chk_mline(lnum) linenr_t lnum; { register char *s; register char *e; char *cs; /* local copy of any modeline found */ char prev; int end; prev = ' '; for (s = nr2ptr(lnum); *s != NUL; ++s) { if (isspace((unsigned char)prev) && (strncmp(s, "vi:", (size_t)3) == 0 || strncmp(s, "ex:", (size_t)3) == 0 || strncmp(s, "vim:", (size_t)4) == 0)) { do ++s; while (s[-1] != ':'); s = cs = strsave(s); if (cs == NULL) break; end = FALSE; while (end == FALSE) { while (*s == ' ' || *s == TAB) ++s; if (*s == NUL) break; for (e = s; (*e != ':' || *(e - 1) == '\\') && *e != NUL; ++e) ; if (*e == NUL) end = TRUE; *e = NUL; if (strncmp(s, "set ", (size_t)4) == 0) /* "vi:set opt opt opt: foo" */ { doset(s + 4); break; } if (doset(s)) /* stop if error found */ break; s = e + 1; } free(cs); break; } prev = *s; } }