blob.c (9380B)
1 2#include "common.h" 3#include "blob.h" 4 5#include <stdlib.h> 6#include <string.h> 7#include <errno.h> 8#include <unistd.h> 9#include <fcntl.h> 10#include <sys/stat.h> 11#include <sys/mman.h> 12 13void blob_init(struct blob *blob) 14{ 15 memset(blob, 0, sizeof(*blob)); 16 history_init(&blob->undo); 17 history_init(&blob->redo); 18} 19 20void blob_replace(struct blob *blob, size_t pos, byte const *data, size_t len, bool save_history) 21{ 22 assert(pos + len <= blob->len); 23 24 if (save_history) { 25 history_free(&blob->redo); 26 history_save(&blob->undo, REPLACE, blob, pos, len); 27 ++blob->saved_dist; 28 } 29 30 if (blob->dirty) 31 for (size_t i = pos / 0x1000; i < (pos + len + 0xfff) / 0x1000; ++i) 32 blob->dirty[i / 8] |= 1 << i % 8; 33 34 memcpy(blob->data + pos, data, len); 35} 36 37void blob_insert(struct blob *blob, size_t pos, byte const *data, size_t len, bool save_history) 38{ 39 assert(pos <= blob->len); 40 assert(blob_can_move(blob)); 41 assert(len); 42 assert(!blob->dirty); /* not implemented */ 43 44 if (save_history) { 45 history_free(&blob->redo); 46 history_save(&blob->undo, INSERT, blob, pos, len); 47 ++blob->saved_dist; 48 } 49 50 blob->data = realloc_strict(blob->data, blob->len += len); 51 52 memmove(blob->data + pos + len, blob->data + pos, blob->len - pos - len); 53 memcpy(blob->data + pos, data, len); 54} 55 56void blob_delete(struct blob *blob, size_t pos, size_t len, bool save_history) 57{ 58 assert(pos + len <= blob->len); 59 assert(blob_can_move(blob)); 60 assert(len); 61 assert(!blob->dirty); /* not implemented */ 62 63 if (save_history) { 64 history_free(&blob->redo); 65 history_save(&blob->undo, DELETE, blob, pos, len); 66 ++blob->saved_dist; 67 } 68 69 memmove(blob->data + pos, blob->data + pos + len, (blob->len -= len) - pos); 70 blob->data = realloc_strict(blob->data, blob->len); 71} 72 73void blob_free(struct blob *blob) 74{ 75 free(blob->filename); 76 77 switch (blob->alloc) { 78 case BLOB_MALLOC: 79 free(blob->data); 80 break; 81 case BLOB_MMAP: 82 free(blob->dirty); 83 munmap_strict(blob->data, blob->len); 84 break; 85 } 86 87 free(blob->clipboard.data); 88 89 history_free(&blob->undo); 90 history_free(&blob->redo); 91} 92 93bool blob_can_move(struct blob const *blob) 94{ 95 return blob->alloc == BLOB_MALLOC; 96} 97 98bool blob_undo(struct blob *blob, size_t *pos) 99{ 100 bool r = history_step(&blob->undo, blob, &blob->redo, pos); 101 blob->saved_dist -= r; 102 return r; 103} 104 105bool blob_redo(struct blob *blob, size_t *pos) 106{ 107 bool r = history_step(&blob->redo, blob, &blob->undo, pos); 108 blob->saved_dist += r; 109 return r; 110} 111 112void blob_yank(struct blob *blob, size_t pos, size_t len) 113{ 114 free(blob->clipboard.data); 115 blob->clipboard.data = NULL; 116 117 if (pos < blob_length(blob)) { 118 blob->clipboard.data = malloc_strict(blob->clipboard.len = len); 119 blob_read_strict(blob, pos, blob->clipboard.data, blob->clipboard.len); 120 } 121} 122 123size_t blob_paste(struct blob *blob, size_t pos, enum op_type type) 124{ 125 if (!blob->clipboard.data) return 0; 126 127 switch (type) { 128 case REPLACE: 129 blob_replace(blob, pos, blob->clipboard.data, min(blob->clipboard.len, blob->len - pos), true); 130 break; 131 case INSERT: 132 blob_insert(blob, pos, blob->clipboard.data, blob->clipboard.len, true); 133 break; 134 default: 135 die("bad operation"); 136 } 137 138 return blob->clipboard.len; 139} 140 141#define DD(F,B) (dir > 0 ? (F) : (B)) 142 143/* modified Boyer-Moore-Horspool algorithm. */ 144static ssize_t blob_search_range(struct blob *blob, byte const *needle, size_t len, size_t start, ssize_t end, ssize_t dir, size_t tab[256]) 145{ 146 size_t blen = blob_length(blob); 147 148 assert(start < blen && end >= -1 && end <= (ssize_t) blen); 149 assert(DD((ssize_t) start <= end, end <= (ssize_t) start)); 150 151 if (len > DD(end-start, start-end)) /* needle longer than range */ 152 return -1; 153 154 for (ssize_t i = start; DD(i < end, i > end) ; ) { 155 156 if (i + len > blen) { 157 /* not enough space for pattern: skip */ 158 i += dir; 159 continue; 160 } 161 assert(i >= 0 && i + len <= blen); 162 163 bool found = true; 164 for (ssize_t j = DD(len-1, 0); found && j >= 0 && (size_t) j < len; j -= dir) 165 found = blob_at(blob, i + j) == needle[j]; 166 if (found) 167 return i; 168 169 i += dir * (ssize_t) tab[blob_at(blob, i + (dir > 0 ? len - 1 : 0))]; 170 171 } 172 173 /* not found */ 174 return -1; 175} 176 177ssize_t blob_search(struct blob *blob, byte const *needle, size_t len, size_t start, ssize_t dir) 178{ 179 size_t blen = blob_length(blob); 180 181 if (!len || len > blen) 182 return -1; 183 184 assert(start < blen); 185 assert(dir == +1 || dir == -1); 186 187 /* could do preprocessing once per needle/dir pair, but patterns are usually short */ 188 size_t tab[256]; 189 for (size_t j = 0; j < 256; ++j) 190 tab[j] = len; 191 for (size_t j = 0; j < len-1; ++j) 192 tab[needle[DD(j, len-1-j)]] = len-1-j; 193 194 ssize_t r = blob_search_range(blob, needle, len, start, DD((ssize_t) blen, -1), dir, tab); 195 if (r < 0) /* wrap around */ 196 r = blob_search_range(blob, needle, len, DD(0, blen-1), start, dir, tab); 197 198 return r; 199} 200 201#undef DD 202 203 204/* blob_load* functions must be called with a fresh struct from blob_init() */ 205 206void blob_load(struct blob *blob, char const *filename) 207{ 208 struct stat st; 209 int fd; 210 void *ptr = NULL; 211 212 if (!filename) 213 return; /* We are creating a new (still unnamed) file */ 214 215 blob->filename = strdup(filename); 216 217 errno = 0; 218 if (stat(filename, &st)) { 219 if (errno != ENOENT) 220 pdie("stat"); 221 return; /* We are creating a new file with given name */ 222 } 223 224 if (0 > (fd = open(filename, O_RDONLY))) 225 pdie("open"); 226 227 switch (st.st_mode & S_IFMT) { 228 case S_IFREG: 229 blob->len = st.st_size; 230 blob->alloc = blob->len >= CONFIG_LARGE_FILESIZE ? BLOB_MMAP : BLOB_MALLOC; 231 break; 232 case S_IFBLK: 233 blob->len = lseek_strict(fd, 0, SEEK_END); 234 blob->alloc = BLOB_MMAP; 235 break; 236 default: 237 die("unsupported file type"); 238 } 239 240 if (blob->len) 241 ptr = mmap_strict(NULL, 242 blob->len, 243 PROT_READ | PROT_WRITE, 244 MAP_PRIVATE | MAP_NORESERVE, 245 fd, 246 0); 247 248 switch (blob->alloc) { 249 250 case BLOB_MMAP: 251 assert(ptr); 252 blob->data = ptr; 253 if (!(blob->dirty = calloc(((blob->len + 0xfff) / 0x1000 + 7) / 8, sizeof(*blob->dirty)))) 254 pdie("calloc"); 255 break; 256 257 case BLOB_MALLOC: 258 blob->data = malloc_strict(blob->len); 259 if (ptr) { 260 memcpy(blob->data, ptr, blob->len); 261 munmap_strict(ptr, blob->len); 262 } 263 break; 264 265 default: 266 die("bad blob type"); 267 } 268 269 if (close(fd)) 270 pdie("close"); 271} 272 273void blob_load_stream(struct blob *blob, FILE *fp) 274{ 275 const size_t alloc_size = 0x1000; 276 size_t n = 0; 277 278 while (true) { 279 assert(n <= blob->len); 280 281 if (blob->len - n < alloc_size) 282 blob->data = realloc_strict(blob->data, (blob->len += alloc_size)); 283 284 size_t r = fread(blob->data + n, 1, blob->len - n, fp); 285 if (!r) { 286 if (feof(fp)) break; 287 pdie("could not read data from stream"); 288 } 289 n += r; 290 } 291 blob->data = realloc(blob->data, (blob->len = n)); 292} 293 294enum blob_save_error blob_save(struct blob *blob, char const *filename) 295{ 296 int fd; 297 struct stat st; 298 byte const *ptr; 299 300 if (filename) { 301 free(blob->filename); 302 blob->filename = strdup(filename); 303 } 304 else if (blob->filename) 305 filename = blob->filename; 306 else 307 return BLOB_SAVE_FILENAME; 308 309 errno = 0; 310 if (0 > (fd = open(filename, 311 O_WRONLY | O_CREAT, 312 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { 313 switch (errno) { 314 case ENOENT: return BLOB_SAVE_NONEXISTENT; 315 case EACCES: return BLOB_SAVE_PERMISSIONS; 316 case ETXTBSY: return BLOB_SAVE_BUSY; 317 default: pdie("open"); 318 } 319 } 320 321 if (fstat(fd, &st)) 322 pdie("fstat"); 323 324 if ((st.st_mode & S_IFMT) == S_IFREG && ftruncate(fd, blob->len)) 325 pdie("ftruncate"); 326 327 for (size_t i = 0, n; i < blob->len; i += n) { 328 329 if (blob->dirty && !(blob->dirty[i / 0x1000 / 8] & (1 << i / 0x1000 % 8))) { 330 n = 0x1000 - i % 0x1000; 331 continue; 332 } 333 334 ptr = blob_lookup(blob, i, &n); 335 if (blob->dirty) 336 n = min(0x1000 - i % 0x1000, n); 337 338 if ((ssize_t) i != lseek(fd, i, SEEK_SET)) 339 pdie("lseek"); 340 341 if (0 >= (n = write(fd, ptr, n))) 342 pdie("write"); 343 } 344 345 if (close(fd)) 346 pdie("close"); 347 348 blob->saved_dist = 0; 349 350 return BLOB_SAVE_OK; 351} 352 353bool blob_is_saved(struct blob const *blob) 354{ 355 return !blob->saved_dist; 356} 357 358byte const *blob_lookup(struct blob const *blob, size_t pos, size_t *len) 359{ 360 assert(pos < blob->len); 361 362 if (len) 363 *len = blob->len - pos; 364 return blob->data + pos; 365} 366 367void blob_read_strict(struct blob *blob, size_t pos, byte *buf, size_t len) 368{ 369 byte const *ptr; 370 for (size_t i = 0, n; i < len; i += n) { 371 ptr = blob_lookup(blob, pos, &n); 372 memcpy(buf + i, ptr, (n = min(len - i, n))); 373 } 374} 375