hyx

Minimalist but powerful terminal hex editor
git clone https://git.sinitax.com/yx7/hyx
Log | Files | Refs | sfeed.txt

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