#include #include #include #include #include #include #include #include #include #include #include #include #include "buf.h" void buf_dump(struct buf *b) { struct line *l; int i, j; i = 0; TAILQ_FOREACH(l, &b->lines, list) { fprintf(stderr, "%d: (%d) ", i, l->len); for (j = 0; j < l->len; j++) putc(l->line[j], stderr); i++; } putc('\n', stderr); fflush(stderr); } struct line * line_next(struct line *l) { return (TAILQ_NEXT(l, list)); } struct line * line_prev(struct line *l) { return (TAILQ_PREV(l, lines_head, list)); } void line_add(struct buf *b, struct line *l, int pos) { struct line *c; if (pos == 0) { TAILQ_INSERT_HEAD(&b->lines, l, list); } else { /* XXX cache current line? */ c = buf_get_line(b, pos - 1); if (c == NULL) abort(); TAILQ_INSERT_AFTER(&b->lines, c, l, list); } b->num_lines++; } void line_add_undo(struct buf *b, struct line *l, int y) { undo_add_line(l, y, UND_ADD); line_add(b, l, y); } void line_add_partial(struct buf *b, struct line *l, int start, int end, int pos) { struct line *n; char *t; t = malloc(end - start); if (t == NULL) abort(); memcpy(t, l->line + start, end - start); n = line_alloc(t, end - start); if (n == NULL) abort(); line_add(b, n, pos); } void line_remove(struct buf *b, struct line *l) { TAILQ_REMOVE(&b->lines, l, list); b->num_lines--; } void line_remove_undo(struct buf *b, struct line *l, int y) { undo_add_line(l, y, UND_DEL); line_remove(b, l); } struct line * line_alloc(char *line, int len) { struct line *l; l = malloc(sizeof(struct line)); if (l == NULL) return (NULL); l->line = line; l->len = len; return (l); } void line_replace(struct line *l, char *text, int len) { free(l->line); l->line = text; l->len = len; } void line_replace_undo(struct line *l, char *text, int len, int y) { undo_add_line(l, y, UND_MOD); line_replace(l, text, len); } struct line * line_dup(struct line *l) { char *t; t = malloc(l->len); memcpy(t, l->line, l->len); return (line_alloc(t, l->len)); } void line_free(struct line *l) { free(l->line); free(l); } struct line * buf_get_line(struct buf *b, int num) { struct line *l; int i; if (b->num_lines == 0) return (NULL); if (num > b->num_lines) num = b->num_lines; l = TAILQ_FIRST(&b->lines); for (i = 0; i < num; i++) l = TAILQ_NEXT(l, list); return (l); } int buf_find_line(struct buf *b, struct line *line) { struct line *l; int y; y = 0; TAILQ_FOREACH(l, &b->lines, list) { if (l == line) return (y); y++; } return (-1); } void buf_init(struct buf *b) { TAILQ_INIT(&b->lines); b->num_lines = 0; b->dirty = 0; } void buf_clear(struct buf *b) { struct line *l, *l2; TAILQ_FOREACH_SAFE(l, &b->lines, list, l2) { line_remove(b, l); line_free(l); } } void buf_union(struct buf *b1, struct buf *b2, int pos) { struct line *l, *o, *t; int i; i = 0; TAILQ_FOREACH_SAFE(l, &b2->lines, list, t) { /* The first line of b2 replaces line in b1 */ if (i == 0) { o = buf_get_line(b1, pos); line_replace_undo(o, l->line, l->len, pos); line_remove(b2, l); free(l); i++; continue; } line_remove(b2, l); line_add_undo(b1, l, pos + i); i++; } } int file_load(struct file *f, struct buf *b) { struct line *l; struct stat st; char *c, *m, *n, *t; int fd, i, ret; strlcpy(f->dir, dirname(f->path), PATHSIZE); if ((fd = open(f->path, O_RDONLY)) == -1) return (errno); if (fstat(fd, &st) < 0) return (errno); f->mode = st.st_mode; m = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (m == MAP_FAILED) return (errno); ret = 0; i = 0; n = m; while (n < m + st.st_size) { if ((c = memchr(n, '\n', st.st_size - (n - m))) == NULL) c = m + st.st_size - 1; if ((t = malloc(c - n)) == NULL) { ret = ENOMEM; goto out; } memcpy(t, n, c - n); if ((l = line_alloc(t, c - n)) == NULL) { ret = ENOMEM; goto out; } line_add(b, l, i); i++; n = c + 1; } out: munmap(m, st.st_size); return (ret); } int file_store(struct file *f, struct buf *b) { struct line *l; char tmp_path[PATHSIZE]; int fd, ret, tmp; /* XXX check mtime */ /* Try to safely overwrite the file if possible. */ tmp = 1; snprintf(tmp_path, PATHSIZE, "%s/%s", f->dir, "reXXXX"); fd = mkstemp(tmp_path); if (fd == -1) { tmp = 0; if ((fd = open(f->path, O_WRONLY | O_CREAT)) != -1) return (errno); /* XXX truncate to correct size AFTER writing */ if (ftruncate(fd, 0) != 0) { ret = errno; goto del; } } TAILQ_FOREACH(l, &b->lines, list) { /* XXX short writes */ if ((ret = write(fd, l->line, l->len)) == -1) { ret = errno; goto del; } if ((ret = write(fd, "\n", 1)) == -1) { ret = errno; goto del; } } if ((ret = fsync(fd)) != 0) { ret = errno; goto del; } if ((ret = fchmod(fd, f->mode)) != 0) { ret = errno; goto del; } if ((ret = close(fd)) != 0) { ret = errno; goto del; } if (tmp) { if ((ret = rename(tmp_path, f->path)) != 0) { ret = errno; goto del; } unlink(tmp_path); } b->dirty = 0; return (0); del: close(fd); if (tmp) unlink(tmp_path); return (ret); }