|
Lines 1-6
Link Here
|
| 1 |
#ifndef _GNU_SOURCE |
|
|
| 2 |
#define _GNU_SOURCE /* memrchr(3) is non-standard */ |
| 3 |
#endif |
| 4 |
#include <unistd.h> |
1 |
#include <unistd.h> |
| 5 |
#include <stdio.h> |
2 |
#include <stdio.h> |
| 6 |
#include <stdlib.h> |
3 |
#include <stdlib.h> |
|
Lines 15-62
Link Here
|
| 15 |
#include <sys/types.h> |
12 |
#include <sys/types.h> |
| 16 |
#include <sys/stat.h> |
13 |
#include <sys/stat.h> |
| 17 |
#include <sys/mman.h> |
14 |
#include <sys/mman.h> |
| 18 |
#if CONFIG_ACL |
|
|
| 19 |
#include <sys/acl.h> |
| 20 |
#endif |
| 21 |
#if CONFIG_SELINUX |
| 22 |
#include <selinux/selinux.h> |
| 23 |
#endif |
| 24 |
|
15 |
|
| 25 |
#include "text.h" |
16 |
#include "text.h" |
| 26 |
#include "text-util.h" |
17 |
#include "text-util.h" |
| 27 |
#include "text-motions.h" |
18 |
#include "text-motions.h" |
| 28 |
#include "util.h" |
19 |
#include "util.h" |
|
|
20 |
#include "array.h" |
| 21 |
#include "text-internal.h" |
| 29 |
|
22 |
|
| 30 |
/* Allocate blocks holding the actual file content in junks of size: */ |
|
|
| 31 |
#ifndef BLOCK_SIZE |
| 32 |
#define BLOCK_SIZE (1 << 20) |
| 33 |
#endif |
| 34 |
/* Files smaller than this value are copied on load, larger ones are mmap(2)-ed |
| 35 |
* directely. Hence the former can be truncated, while doing so on the latter |
| 36 |
* results in havoc. */ |
| 37 |
#define BLOCK_MMAP_SIZE (1 << 26) |
| 38 |
|
| 39 |
/* Block holding the file content, either readonly mmap(2)-ed from the original |
| 40 |
* file or heap allocated to store the modifications. |
| 41 |
*/ |
| 42 |
typedef struct Block Block; |
| 43 |
struct Block { |
| 44 |
size_t size; /* maximal capacity */ |
| 45 |
size_t len; /* current used length / insertion position */ |
| 46 |
char *data; /* actual data */ |
| 47 |
enum { /* type of allocation */ |
| 48 |
MMAP_ORIG, /* mmap(2)-ed from an external file */ |
| 49 |
MMAP, /* mmap(2)-ed from a temporary file only known to this process */ |
| 50 |
MALLOC, /* heap allocated block using malloc(3) */ |
| 51 |
} type; |
| 52 |
Block *next; /* next junk */ |
| 53 |
}; |
| 54 |
|
| 55 |
/* A piece holds a reference (but doesn't itself store) a certain amount of data. |
23 |
/* A piece holds a reference (but doesn't itself store) a certain amount of data. |
| 56 |
* All active pieces chained together form the whole content of the document. |
24 |
* All active pieces chained together form the whole content of the document. |
| 57 |
* At the beginning there exists only one piece, spanning the whole document. |
25 |
* At the beginning there exists only one piece, spanning the whole document. |
| 58 |
* Upon insertion/deletion new pieces will be created to represent the changes. |
26 |
* Upon insertion/deletion new pieces will be created to represent the changes. |
| 59 |
* Generally pieces are never destroyed, but kept around to peform undo/redo |
27 |
* Generally pieces are never destroyed, but kept around to perform undo/redo |
| 60 |
* operations. |
28 |
* operations. |
| 61 |
*/ |
29 |
*/ |
| 62 |
struct Piece { |
30 |
struct Piece { |
|
Lines 115-122
Link Here
|
| 115 |
|
83 |
|
| 116 |
/* The main struct holding all information of a given file */ |
84 |
/* The main struct holding all information of a given file */ |
| 117 |
struct Text { |
85 |
struct Text { |
| 118 |
Block *block; /* original file content at the time of load operation */ |
86 |
Array blocks; /* blocks which hold text content */ |
| 119 |
Block *blocks; /* all blocks which have been allocated to hold insertion data */ |
|
|
| 120 |
Piece *pieces; /* all pieces which have been allocated, used to free them */ |
87 |
Piece *pieces; /* all pieces which have been allocated, used to free them */ |
| 121 |
Piece *cache; /* most recently modified piece */ |
88 |
Piece *cache; /* most recently modified piece */ |
| 122 |
Piece begin, end; /* sentinel nodes which always exists but don't hold any data */ |
89 |
Piece begin, end; /* sentinel nodes which always exists but don't hold any data */ |
|
Lines 129-151
Link Here
|
| 129 |
LineCache lines; /* mapping between absolute pos in bytes and logical line breaks */ |
96 |
LineCache lines; /* mapping between absolute pos in bytes and logical line breaks */ |
| 130 |
}; |
97 |
}; |
| 131 |
|
98 |
|
| 132 |
struct TextSave { /* used to hold context between text_save_{begin,commit} calls */ |
|
|
| 133 |
Text *txt; /* text to operate on */ |
| 134 |
char *filename; /* filename to save to as given to text_save_begin */ |
| 135 |
char *tmpname; /* temporary name used for atomic rename(2) */ |
| 136 |
int fd; /* file descriptor to write data to using text_save_write */ |
| 137 |
enum TextSaveMethod type; /* method used to save file */ |
| 138 |
}; |
| 139 |
|
| 140 |
/* block management */ |
99 |
/* block management */ |
| 141 |
static Block *block_alloc(Text*, size_t size); |
|
|
| 142 |
static Block *block_read(Text*, size_t size, int fd); |
| 143 |
static Block *block_mmap(Text*, size_t size, int fd, off_t offset); |
| 144 |
static void block_free(Block*); |
| 145 |
static bool block_capacity(Block*, size_t len); |
| 146 |
static const char *block_append(Block*, const char *data, size_t len); |
| 147 |
static bool block_insert(Block*, size_t pos, const char *data, size_t len); |
| 148 |
static bool block_delete(Block*, size_t pos, size_t len); |
| 149 |
static const char *block_store(Text*, const char *data, size_t len); |
100 |
static const char *block_store(Text*, const char *data, size_t len); |
| 150 |
/* cache layer */ |
101 |
/* cache layer */ |
| 151 |
static void cache_piece(Text *txt, Piece *p); |
102 |
static void cache_piece(Text *txt, Piece *p); |
|
Lines 157-163
Link Here
|
| 157 |
static void piece_free(Piece *p); |
108 |
static void piece_free(Piece *p); |
| 158 |
static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len); |
109 |
static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len); |
| 159 |
static Location piece_get_intern(Text *txt, size_t pos); |
110 |
static Location piece_get_intern(Text *txt, size_t pos); |
| 160 |
static Location piece_get_extern(Text *txt, size_t pos); |
111 |
static Location piece_get_extern(const Text *txt, size_t pos); |
| 161 |
/* span management */ |
112 |
/* span management */ |
| 162 |
static void span_init(Span *span, Piece *start, Piece *end); |
113 |
static void span_init(Span *span, Piece *start, Piece *end); |
| 163 |
static void span_swap(Text *txt, Span *old, Span *new); |
114 |
static void span_swap(Text *txt, Span *old, Span *new); |
|
Lines 172-316
Link Here
|
| 172 |
static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skiped); |
123 |
static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skiped); |
| 173 |
static size_t lines_count(Text *txt, size_t pos, size_t len); |
124 |
static size_t lines_count(Text *txt, size_t pos, size_t len); |
| 174 |
|
125 |
|
| 175 |
static ssize_t write_all(int fd, const char *buf, size_t count) { |
126 |
/* stores the given data in a block, allocates a new one if necessary. Returns |
| 176 |
size_t rem = count; |
127 |
* a pointer to the storage location or NULL if allocation failed. */ |
| 177 |
while (rem > 0) { |
128 |
static const char *block_store(Text *txt, const char *data, size_t len) { |
| 178 |
ssize_t written = write(fd, buf, rem > INT_MAX ? INT_MAX : rem); |
129 |
Block *blk = array_get_ptr(&txt->blocks, array_length(&txt->blocks)-1); |
| 179 |
if (written < 0) { |
130 |
if (!blk || !block_capacity(blk, len)) { |
| 180 |
if (errno == EAGAIN || errno == EINTR) |
131 |
blk = block_alloc(len); |
| 181 |
continue; |
132 |
if (!blk) |
| 182 |
return -1; |
133 |
return NULL; |
| 183 |
} else if (written == 0) { |
134 |
if (!array_add_ptr(&txt->blocks, blk)) { |
| 184 |
break; |
|
|
| 185 |
} |
| 186 |
rem -= written; |
| 187 |
buf += written; |
| 188 |
} |
| 189 |
return count - rem; |
| 190 |
} |
| 191 |
|
| 192 |
/* allocate a new block of MAX(size, BLOCK_SIZE) bytes */ |
| 193 |
static Block *block_alloc(Text *txt, size_t size) { |
| 194 |
Block *blk = calloc(1, sizeof *blk); |
| 195 |
if (!blk) |
| 196 |
return NULL; |
| 197 |
if (BLOCK_SIZE > size) |
| 198 |
size = BLOCK_SIZE; |
| 199 |
if (!(blk->data = malloc(size))) { |
| 200 |
free(blk); |
| 201 |
return NULL; |
| 202 |
} |
| 203 |
blk->type = MALLOC; |
| 204 |
blk->size = size; |
| 205 |
blk->next = txt->blocks; |
| 206 |
txt->blocks = blk; |
| 207 |
return blk; |
| 208 |
} |
| 209 |
|
| 210 |
static Block *block_read(Text *txt, size_t size, int fd) { |
| 211 |
Block *blk = block_alloc(txt, size); |
| 212 |
if (!blk) |
| 213 |
return NULL; |
| 214 |
while (size > 0) { |
| 215 |
char data[4096]; |
| 216 |
ssize_t len = read(fd, data, MIN(sizeof(data), size)); |
| 217 |
if (len == -1) { |
| 218 |
txt->blocks = blk->next; |
| 219 |
block_free(blk); |
135 |
block_free(blk); |
| 220 |
return NULL; |
136 |
return NULL; |
| 221 |
} else if (len == 0) { |
|
|
| 222 |
break; |
| 223 |
} else { |
| 224 |
block_append(blk, data, len); |
| 225 |
size -= len; |
| 226 |
} |
137 |
} |
| 227 |
} |
138 |
} |
| 228 |
return blk; |
|
|
| 229 |
} |
| 230 |
|
| 231 |
static Block *block_mmap(Text *txt, size_t size, int fd, off_t offset) { |
| 232 |
Block *blk = calloc(1, sizeof *blk); |
| 233 |
if (!blk) |
| 234 |
return NULL; |
| 235 |
if (size) { |
| 236 |
blk->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset); |
| 237 |
if (blk->data == MAP_FAILED) { |
| 238 |
free(blk); |
| 239 |
return NULL; |
| 240 |
} |
| 241 |
} |
| 242 |
blk->type = MMAP_ORIG; |
| 243 |
blk->size = size; |
| 244 |
blk->len = size; |
| 245 |
blk->next = txt->blocks; |
| 246 |
txt->blocks = blk; |
| 247 |
return blk; |
| 248 |
} |
| 249 |
|
| 250 |
static void block_free(Block *blk) { |
| 251 |
if (!blk) |
| 252 |
return; |
| 253 |
if (blk->type == MALLOC) |
| 254 |
free(blk->data); |
| 255 |
else if ((blk->type == MMAP_ORIG || blk->type == MMAP) && blk->data) |
| 256 |
munmap(blk->data, blk->size); |
| 257 |
free(blk); |
| 258 |
} |
| 259 |
|
| 260 |
/* check whether block has enough free space to store len bytes */ |
| 261 |
static bool block_capacity(Block *blk, size_t len) { |
| 262 |
return blk->size - blk->len >= len; |
| 263 |
} |
| 264 |
|
| 265 |
/* append data to block, assumes there is enough space available */ |
| 266 |
static const char *block_append(Block *blk, const char *data, size_t len) { |
| 267 |
char *dest = memcpy(blk->data + blk->len, data, len); |
| 268 |
blk->len += len; |
| 269 |
return dest; |
| 270 |
} |
| 271 |
|
| 272 |
/* stores the given data in a block, allocates a new one if necessary. returns |
| 273 |
* a pointer to the storage location or NULL if allocation failed. */ |
| 274 |
static const char *block_store(Text *txt, const char *data, size_t len) { |
| 275 |
Block *blk = txt->blocks; |
| 276 |
if ((!blk || !block_capacity(blk, len)) && !(blk = block_alloc(txt, len))) |
| 277 |
return NULL; |
| 278 |
return block_append(blk, data, len); |
139 |
return block_append(blk, data, len); |
| 279 |
} |
140 |
} |
| 280 |
|
141 |
|
| 281 |
/* insert data into block at an arbitrary position, this should only be used with |
|
|
| 282 |
* data of the most recently created piece. */ |
| 283 |
static bool block_insert(Block *blk, size_t pos, const char *data, size_t len) { |
| 284 |
if (pos > blk->len || !block_capacity(blk, len)) |
| 285 |
return false; |
| 286 |
if (blk->len == pos) |
| 287 |
return block_append(blk, data, len); |
| 288 |
char *insert = blk->data + pos; |
| 289 |
memmove(insert + len, insert, blk->len - pos); |
| 290 |
memcpy(insert, data, len); |
| 291 |
blk->len += len; |
| 292 |
return true; |
| 293 |
} |
| 294 |
|
| 295 |
/* delete data from a block at an arbitrary position, this should only be used with |
| 296 |
* data of the most recently created piece. */ |
| 297 |
static bool block_delete(Block *blk, size_t pos, size_t len) { |
| 298 |
size_t end; |
| 299 |
if (!addu(pos, len, &end) || end > blk->len) |
| 300 |
return false; |
| 301 |
if (blk->len == pos) { |
| 302 |
blk->len -= len; |
| 303 |
return true; |
| 304 |
} |
| 305 |
char *delete = blk->data + pos; |
| 306 |
memmove(delete, delete + len, blk->len - pos - len); |
| 307 |
blk->len -= len; |
| 308 |
return true; |
| 309 |
} |
| 310 |
|
| 311 |
/* cache the given piece if it is the most recently changed one */ |
142 |
/* cache the given piece if it is the most recently changed one */ |
| 312 |
static void cache_piece(Text *txt, Piece *p) { |
143 |
static void cache_piece(Text *txt, Piece *p) { |
| 313 |
Block *blk = txt->blocks; |
144 |
Block *blk = array_get_ptr(&txt->blocks, array_length(&txt->blocks)-1); |
| 314 |
if (!blk || p->data < blk->data || p->data + p->len != blk->data + blk->len) |
145 |
if (!blk || p->data < blk->data || p->data + p->len != blk->data + blk->len) |
| 315 |
return; |
146 |
return; |
| 316 |
txt->cache = p; |
147 |
txt->cache = p; |
|
Lines 318-324
Link Here
|
| 318 |
|
149 |
|
| 319 |
/* check whether the given piece was the most recently modified one */ |
150 |
/* check whether the given piece was the most recently modified one */ |
| 320 |
static bool cache_contains(Text *txt, Piece *p) { |
151 |
static bool cache_contains(Text *txt, Piece *p) { |
| 321 |
Block *blk = txt->blocks; |
152 |
Block *blk = array_get_ptr(&txt->blocks, array_length(&txt->blocks)-1); |
| 322 |
Revision *rev = txt->current_revision; |
153 |
Revision *rev = txt->current_revision; |
| 323 |
if (!blk || !txt->cache || txt->cache != p || !rev || !rev->change) |
154 |
if (!blk || !txt->cache || txt->cache != p || !rev || !rev->change) |
| 324 |
return false; |
155 |
return false; |
|
Lines 336-348
Link Here
|
| 336 |
return found && p->data + p->len == blk->data + blk->len; |
167 |
return found && p->data + p->len == blk->data + blk->len; |
| 337 |
} |
168 |
} |
| 338 |
|
169 |
|
| 339 |
/* try to insert a junk of data at a given piece offset. the insertion is only |
170 |
/* try to insert a chunk of data at a given piece offset. The insertion is only |
| 340 |
* performed if the piece is the most recenetly changed one. the legnth of the |
171 |
* performed if the piece is the most recently changed one. The length of the |
| 341 |
* piece, the span containing it and the whole text is adjusted accordingly */ |
172 |
* piece, the span containing it and the whole text is adjusted accordingly */ |
| 342 |
static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len) { |
173 |
static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len) { |
| 343 |
if (!cache_contains(txt, p)) |
174 |
if (!cache_contains(txt, p)) |
| 344 |
return false; |
175 |
return false; |
| 345 |
Block *blk = txt->blocks; |
176 |
Block *blk = array_get_ptr(&txt->blocks, array_length(&txt->blocks)-1); |
| 346 |
size_t bufpos = p->data + off - blk->data; |
177 |
size_t bufpos = p->data + off - blk->data; |
| 347 |
if (!block_insert(blk, bufpos, data, len)) |
178 |
if (!block_insert(blk, bufpos, data, len)) |
| 348 |
return false; |
179 |
return false; |
|
Lines 352-365
Link Here
|
| 352 |
return true; |
183 |
return true; |
| 353 |
} |
184 |
} |
| 354 |
|
185 |
|
| 355 |
/* try to delete a junk of data at a given piece offset. the deletion is only |
186 |
/* try to delete a chunk of data at a given piece offset. The deletion is only |
| 356 |
* performed if the piece is the most recenetly changed one and the whole |
187 |
* performed if the piece is the most recently changed one and the whole |
| 357 |
* affected range lies within it. the legnth of the piece, the span containing it |
188 |
* affected range lies within it. The length of the piece, the span containing it |
| 358 |
* and the whole text is adjusted accordingly */ |
189 |
* and the whole text is adjusted accordingly */ |
| 359 |
static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len) { |
190 |
static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len) { |
| 360 |
if (!cache_contains(txt, p)) |
191 |
if (!cache_contains(txt, p)) |
| 361 |
return false; |
192 |
return false; |
| 362 |
Block *blk = txt->blocks; |
193 |
Block *blk = array_get_ptr(&txt->blocks, array_length(&txt->blocks)-1); |
| 363 |
size_t end; |
194 |
size_t end; |
| 364 |
size_t bufpos = p->data + off - blk->data; |
195 |
size_t bufpos = p->data + off - blk->data; |
| 365 |
if (!addu(off, len, &end) || end > p->len || !block_delete(blk, bufpos, len)) |
196 |
if (!addu(off, len, &end) || end > p->len || !block_delete(blk, bufpos, len)) |
|
Lines 485-493
Link Here
|
| 485 |
p->len = len; |
316 |
p->len = len; |
| 486 |
} |
317 |
} |
| 487 |
|
318 |
|
| 488 |
/* returns the piece holding the text at byte offset pos. if pos happens to |
319 |
/* returns the piece holding the text at byte offset pos. If pos happens to |
| 489 |
* be at a piece boundry i.e. the first byte of a piece then the previous piece |
320 |
* be at a piece boundary i.e. the first byte of a piece then the previous piece |
| 490 |
* to the left is returned with an offset of piece->len. this is convenient for |
321 |
* to the left is returned with an offset of piece->len. This is convenient for |
| 491 |
* modifications to the piece chain where both pieces (the returned one and the |
322 |
* modifications to the piece chain where both pieces (the returned one and the |
| 492 |
* one following it) are needed, but unsuitable as a public interface. |
323 |
* one following it) are needed, but unsuitable as a public interface. |
| 493 |
* |
324 |
* |
|
Lines 504-515
Link Here
|
| 504 |
return (Location){ 0 }; |
335 |
return (Location){ 0 }; |
| 505 |
} |
336 |
} |
| 506 |
|
337 |
|
| 507 |
/* similiar to piece_get_intern but usable as a public API. returns the piece |
338 |
/* similiar to piece_get_intern but usable as a public API. Returns the piece |
| 508 |
* holding the text at byte offset pos. never returns a sentinel piece. |
339 |
* holding the text at byte offset pos. Never returns a sentinel piece. |
| 509 |
* it pos is the end of file (== text_size()) and the file is not empty then |
340 |
* it pos is the end of file (== text_size()) and the file is not empty then |
| 510 |
* the last piece holding data is returned. |
341 |
* the last piece holding data is returned. |
| 511 |
*/ |
342 |
*/ |
| 512 |
static Location piece_get_extern(Text *txt, size_t pos) { |
343 |
static Location piece_get_extern(const Text *txt, size_t pos) { |
| 513 |
size_t cur = 0; |
344 |
size_t cur = 0; |
| 514 |
Piece *p; |
345 |
Piece *p; |
| 515 |
|
346 |
|
|
Lines 616-623
Link Here
|
| 616 |
span_init(&c->new, new, new); |
447 |
span_init(&c->new, new, new); |
| 617 |
span_init(&c->old, NULL, NULL); |
448 |
span_init(&c->old, NULL, NULL); |
| 618 |
} else { |
449 |
} else { |
| 619 |
/* insert into middle of an existing piece, therfore split the old |
450 |
/* insert into middle of an existing piece, therefore split the old |
| 620 |
* piece. that is we have 3 new pieces one containing the content |
451 |
* piece. That is we have 3 new pieces one containing the content |
| 621 |
* before the insertion point then one holding the newly inserted |
452 |
* before the insertion point then one holding the newly inserted |
| 622 |
* text and one holding the content after the insertion point. |
453 |
* text and one holding the content after the insertion point. |
| 623 |
*/ |
454 |
*/ |
|
Lines 639-675
Link Here
|
| 639 |
return true; |
470 |
return true; |
| 640 |
} |
471 |
} |
| 641 |
|
472 |
|
| 642 |
static bool text_vprintf(Text *txt, size_t pos, const char *format, va_list ap) { |
|
|
| 643 |
va_list ap_save; |
| 644 |
va_copy(ap_save, ap); |
| 645 |
int len = vsnprintf(NULL, 0, format, ap); |
| 646 |
if (len == -1) { |
| 647 |
va_end(ap_save); |
| 648 |
return false; |
| 649 |
} |
| 650 |
char *buf = malloc(len+1); |
| 651 |
bool ret = buf && (vsnprintf(buf, len+1, format, ap_save) == len) && text_insert(txt, pos, buf, len); |
| 652 |
free(buf); |
| 653 |
va_end(ap_save); |
| 654 |
return ret; |
| 655 |
} |
| 656 |
|
| 657 |
bool text_appendf(Text *txt, const char *format, ...) { |
| 658 |
va_list ap; |
| 659 |
va_start(ap, format); |
| 660 |
bool ret = text_vprintf(txt, text_size(txt), format, ap); |
| 661 |
va_end(ap); |
| 662 |
return ret; |
| 663 |
} |
| 664 |
|
| 665 |
bool text_printf(Text *txt, size_t pos, const char *format, ...) { |
| 666 |
va_list ap; |
| 667 |
va_start(ap, format); |
| 668 |
bool ret = text_vprintf(txt, pos, format, ap); |
| 669 |
va_end(ap); |
| 670 |
return ret; |
| 671 |
} |
| 672 |
|
| 673 |
static size_t revision_undo(Text *txt, Revision *rev) { |
473 |
static size_t revision_undo(Text *txt, Revision *rev) { |
| 674 |
size_t pos = EPOS; |
474 |
size_t pos = EPOS; |
| 675 |
for (Change *c = rev->change; c; c = c->next) { |
475 |
for (Change *c = rev->change; c; c = c->next) { |
|
Lines 781-1163
Link Here
|
| 781 |
return history_traverse_to(txt, rev); |
581 |
return history_traverse_to(txt, rev); |
| 782 |
} |
582 |
} |
| 783 |
|
583 |
|
| 784 |
time_t text_state(Text *txt) { |
584 |
time_t text_state(const Text *txt) { |
| 785 |
return txt->history->time; |
585 |
return txt->history->time; |
| 786 |
} |
586 |
} |
| 787 |
|
587 |
|
| 788 |
static bool preserve_acl(int src, int dest) { |
588 |
Text *text_loadat_method(int dirfd, const char *filename, enum TextLoadMethod method) { |
| 789 |
#if CONFIG_ACL |
|
|
| 790 |
acl_t acl = acl_get_fd(src); |
| 791 |
if (!acl) |
| 792 |
return errno == ENOTSUP ? true : false; |
| 793 |
if (acl_set_fd(dest, acl) == -1) { |
| 794 |
acl_free(acl); |
| 795 |
return false; |
| 796 |
} |
| 797 |
acl_free(acl); |
| 798 |
#endif /* CONFIG_ACL */ |
| 799 |
return true; |
| 800 |
} |
| 801 |
|
| 802 |
static bool preserve_selinux_context(int src, int dest) { |
| 803 |
#if CONFIG_SELINUX |
| 804 |
char *context = NULL; |
| 805 |
if (!is_selinux_enabled()) |
| 806 |
return true; |
| 807 |
if (fgetfilecon(src, &context) == -1) |
| 808 |
return errno == ENOTSUP ? true : false; |
| 809 |
if (fsetfilecon(dest, context) == -1) { |
| 810 |
freecon(context); |
| 811 |
return false; |
| 812 |
} |
| 813 |
freecon(context); |
| 814 |
#endif /* CONFIG_SELINUX */ |
| 815 |
return true; |
| 816 |
} |
| 817 |
|
| 818 |
/* Create a new file named `.filename.vis.XXXXXX` (where `XXXXXX` is a |
| 819 |
* randomly generated, unique suffix) and try to preserve all important |
| 820 |
* meta data. After the file content has been written to this temporary |
| 821 |
* file, text_save_commit_atomic will atomically move it to its final |
| 822 |
* (possibly already existing) destination using rename(2). |
| 823 |
* |
| 824 |
* This approach does not work if: |
| 825 |
* |
| 826 |
* - the file is a symbolic link |
| 827 |
* - the file is a hard link |
| 828 |
* - file ownership can not be preserved |
| 829 |
* - file group can not be preserved |
| 830 |
* - directory permissions do not allow creation of a new file |
| 831 |
* - POSXI ACL can not be preserved (if enabled) |
| 832 |
* - SELinux security context can not be preserved (if enabled) |
| 833 |
*/ |
| 834 |
static bool text_save_begin_atomic(TextSave *ctx) { |
| 835 |
int oldfd, saved_errno; |
| 836 |
if ((oldfd = open(ctx->filename, O_RDONLY)) == -1 && errno != ENOENT) |
| 837 |
goto err; |
| 838 |
struct stat oldmeta = { 0 }; |
| 839 |
if (oldfd != -1 && lstat(ctx->filename, &oldmeta) == -1) |
| 840 |
goto err; |
| 841 |
if (oldfd != -1) { |
| 842 |
if (S_ISLNK(oldmeta.st_mode)) /* symbolic link */ |
| 843 |
goto err; |
| 844 |
if (oldmeta.st_nlink > 1) /* hard link */ |
| 845 |
goto err; |
| 846 |
} |
| 847 |
|
| 848 |
char suffix[] = ".vis.XXXXXX"; |
| 849 |
size_t len = strlen(ctx->filename) + sizeof("./.") + sizeof(suffix); |
| 850 |
char *dir = strdup(ctx->filename); |
| 851 |
char *base = strdup(ctx->filename); |
| 852 |
|
| 853 |
if (!(ctx->tmpname = malloc(len)) || !dir || !base) { |
| 854 |
free(dir); |
| 855 |
free(base); |
| 856 |
goto err; |
| 857 |
} |
| 858 |
|
| 859 |
snprintf(ctx->tmpname, len, "%s/.%s%s", dirname(dir), basename(base), suffix); |
| 860 |
free(dir); |
| 861 |
free(base); |
| 862 |
|
| 863 |
if ((ctx->fd = mkstemp(ctx->tmpname)) == -1) |
| 864 |
goto err; |
| 865 |
|
| 866 |
if (oldfd == -1) { |
| 867 |
mode_t mask = umask(0); |
| 868 |
umask(mask); |
| 869 |
if (fchmod(ctx->fd, 0666 & ~mask) == -1) |
| 870 |
goto err; |
| 871 |
} else { |
| 872 |
if (fchmod(ctx->fd, oldmeta.st_mode) == -1) |
| 873 |
goto err; |
| 874 |
if (!preserve_acl(oldfd, ctx->fd) || !preserve_selinux_context(oldfd, ctx->fd)) |
| 875 |
goto err; |
| 876 |
/* change owner if necessary */ |
| 877 |
if (oldmeta.st_uid != getuid() && fchown(ctx->fd, oldmeta.st_uid, (uid_t)-1) == -1) |
| 878 |
goto err; |
| 879 |
/* change group if necessary, in case of failure some editors reset |
| 880 |
* the group permissions to the same as for others */ |
| 881 |
if (oldmeta.st_gid != getgid() && fchown(ctx->fd, (uid_t)-1, oldmeta.st_gid) == -1) |
| 882 |
goto err; |
| 883 |
close(oldfd); |
| 884 |
} |
| 885 |
|
| 886 |
ctx->type = TEXT_SAVE_ATOMIC; |
| 887 |
return true; |
| 888 |
err: |
| 889 |
saved_errno = errno; |
| 890 |
if (oldfd != -1) |
| 891 |
close(oldfd); |
| 892 |
if (ctx->fd != -1) |
| 893 |
close(ctx->fd); |
| 894 |
ctx->fd = -1; |
| 895 |
free(ctx->tmpname); |
| 896 |
ctx->tmpname = NULL; |
| 897 |
errno = saved_errno; |
| 898 |
return false; |
| 899 |
} |
| 900 |
|
| 901 |
static bool text_save_commit_atomic(TextSave *ctx) { |
| 902 |
if (fsync(ctx->fd) == -1) |
| 903 |
return false; |
| 904 |
|
| 905 |
struct stat meta = { 0 }; |
| 906 |
if (fstat(ctx->fd, &meta) == -1) |
| 907 |
return false; |
| 908 |
|
| 909 |
bool close_failed = (close(ctx->fd) == -1); |
| 910 |
ctx->fd = -1; |
| 911 |
if (close_failed) |
| 912 |
return false; |
| 913 |
|
| 914 |
if (rename(ctx->tmpname, ctx->filename) == -1) |
| 915 |
return false; |
| 916 |
|
| 917 |
free(ctx->tmpname); |
| 918 |
ctx->tmpname = NULL; |
| 919 |
|
| 920 |
int dir = open(dirname(ctx->filename), O_DIRECTORY|O_RDONLY); |
| 921 |
if (dir == -1) |
| 922 |
return false; |
| 923 |
|
| 924 |
if (fsync(dir) == -1 && errno != EINVAL) { |
| 925 |
close(dir); |
| 926 |
return false; |
| 927 |
} |
| 928 |
|
| 929 |
if (close(dir) == -1) |
| 930 |
return false; |
| 931 |
|
| 932 |
if (meta.st_mtime) |
| 933 |
ctx->txt->info = meta; |
| 934 |
return true; |
| 935 |
} |
| 936 |
|
| 937 |
static bool text_save_begin_inplace(TextSave *ctx) { |
| 938 |
Text *txt = ctx->txt; |
| 939 |
struct stat meta = { 0 }; |
| 940 |
int newfd = -1, saved_errno; |
| 941 |
if ((ctx->fd = open(ctx->filename, O_CREAT|O_WRONLY, 0666)) == -1) |
| 942 |
goto err; |
| 943 |
if (fstat(ctx->fd, &meta) == -1) |
| 944 |
goto err; |
| 945 |
if (meta.st_dev == txt->info.st_dev && meta.st_ino == txt->info.st_ino && |
| 946 |
txt->block && txt->block->type == MMAP_ORIG && txt->block->size) { |
| 947 |
/* The file we are going to overwrite is currently mmap-ed from |
| 948 |
* text_load, therefore we copy the mmap-ed block to a temporary |
| 949 |
* file and remap it at the same position such that all pointers |
| 950 |
* from the various pieces are still valid. |
| 951 |
*/ |
| 952 |
size_t size = txt->block->size; |
| 953 |
char tmpname[32] = "/tmp/vis-XXXXXX"; |
| 954 |
newfd = mkstemp(tmpname); |
| 955 |
if (newfd == -1) |
| 956 |
goto err; |
| 957 |
if (unlink(tmpname) == -1) |
| 958 |
goto err; |
| 959 |
ssize_t written = write_all(newfd, txt->block->data, size); |
| 960 |
if (written == -1 || (size_t)written != size) |
| 961 |
goto err; |
| 962 |
if (munmap(txt->block->data, size) == -1) |
| 963 |
goto err; |
| 964 |
|
| 965 |
void *data = mmap(txt->block->data, size, PROT_READ, MAP_SHARED, newfd, 0); |
| 966 |
if (data == MAP_FAILED) |
| 967 |
goto err; |
| 968 |
if (data != txt->block->data) { |
| 969 |
munmap(data, size); |
| 970 |
goto err; |
| 971 |
} |
| 972 |
bool close_failed = (close(newfd) == -1); |
| 973 |
newfd = -1; |
| 974 |
if (close_failed) |
| 975 |
goto err; |
| 976 |
txt->block->data = data; |
| 977 |
txt->block->type = MMAP; |
| 978 |
newfd = -1; |
| 979 |
} |
| 980 |
/* overwrite the existing file content, if something goes wrong |
| 981 |
* here we are screwed, TODO: make a backup before? */ |
| 982 |
if (ftruncate(ctx->fd, 0) == -1) |
| 983 |
goto err; |
| 984 |
ctx->type = TEXT_SAVE_INPLACE; |
| 985 |
return true; |
| 986 |
err: |
| 987 |
saved_errno = errno; |
| 988 |
if (newfd != -1) |
| 989 |
close(newfd); |
| 990 |
if (ctx->fd != -1) |
| 991 |
close(ctx->fd); |
| 992 |
ctx->fd = -1; |
| 993 |
errno = saved_errno; |
| 994 |
return false; |
| 995 |
} |
| 996 |
|
| 997 |
static bool text_save_commit_inplace(TextSave *ctx) { |
| 998 |
if (fsync(ctx->fd) == -1) |
| 999 |
return false; |
| 1000 |
struct stat meta = { 0 }; |
| 1001 |
if (fstat(ctx->fd, &meta) == -1) |
| 1002 |
return false; |
| 1003 |
if (close(ctx->fd) == -1) |
| 1004 |
return false; |
| 1005 |
ctx->txt->info = meta; |
| 1006 |
return true; |
| 1007 |
} |
| 1008 |
|
| 1009 |
TextSave *text_save_begin(Text *txt, const char *filename, enum TextSaveMethod type) { |
| 1010 |
if (!filename) |
| 1011 |
return NULL; |
| 1012 |
TextSave *ctx = calloc(1, sizeof *ctx); |
| 1013 |
if (!ctx) |
| 1014 |
return NULL; |
| 1015 |
ctx->txt = txt; |
| 1016 |
ctx->fd = -1; |
| 1017 |
if (!(ctx->filename = strdup(filename))) |
| 1018 |
goto err; |
| 1019 |
errno = 0; |
| 1020 |
if ((type == TEXT_SAVE_AUTO || type == TEXT_SAVE_ATOMIC) && text_save_begin_atomic(ctx)) |
| 1021 |
return ctx; |
| 1022 |
if (errno == ENOSPC) |
| 1023 |
goto err; |
| 1024 |
if ((type == TEXT_SAVE_AUTO || type == TEXT_SAVE_INPLACE) && text_save_begin_inplace(ctx)) |
| 1025 |
return ctx; |
| 1026 |
err: |
| 1027 |
text_save_cancel(ctx); |
| 1028 |
return NULL; |
| 1029 |
} |
| 1030 |
|
| 1031 |
bool text_save_commit(TextSave *ctx) { |
| 1032 |
if (!ctx) |
| 1033 |
return true; |
| 1034 |
bool ret; |
| 1035 |
Text *txt = ctx->txt; |
| 1036 |
switch (ctx->type) { |
| 1037 |
case TEXT_SAVE_ATOMIC: |
| 1038 |
ret = text_save_commit_atomic(ctx); |
| 1039 |
break; |
| 1040 |
case TEXT_SAVE_INPLACE: |
| 1041 |
ret = text_save_commit_inplace(ctx); |
| 1042 |
break; |
| 1043 |
default: |
| 1044 |
ret = false; |
| 1045 |
break; |
| 1046 |
} |
| 1047 |
|
| 1048 |
if (ret) { |
| 1049 |
txt->saved_revision = txt->history; |
| 1050 |
text_snapshot(txt); |
| 1051 |
} |
| 1052 |
text_save_cancel(ctx); |
| 1053 |
return ret; |
| 1054 |
} |
| 1055 |
|
| 1056 |
void text_save_cancel(TextSave *ctx) { |
| 1057 |
if (!ctx) |
| 1058 |
return; |
| 1059 |
int saved_errno = errno; |
| 1060 |
if (ctx->fd != -1) |
| 1061 |
close(ctx->fd); |
| 1062 |
if (ctx->tmpname && ctx->tmpname[0]) |
| 1063 |
unlink(ctx->tmpname); |
| 1064 |
free(ctx->tmpname); |
| 1065 |
free(ctx->filename); |
| 1066 |
free(ctx); |
| 1067 |
errno = saved_errno; |
| 1068 |
} |
| 1069 |
|
| 1070 |
/* First try to save the file atomically using rename(2) if this does not |
| 1071 |
* work overwrite the file in place. However if something goes wrong during |
| 1072 |
* this overwrite the original file is permanently damaged. |
| 1073 |
*/ |
| 1074 |
bool text_save(Text *txt, const char *filename) { |
| 1075 |
return text_save_method(txt, filename, TEXT_SAVE_AUTO); |
| 1076 |
} |
| 1077 |
|
| 1078 |
bool text_save_method(Text *txt, const char *filename, enum TextSaveMethod method) { |
| 1079 |
if (!filename) { |
| 1080 |
txt->saved_revision = txt->history; |
| 1081 |
text_snapshot(txt); |
| 1082 |
return true; |
| 1083 |
} |
| 1084 |
TextSave *ctx = text_save_begin(txt, filename, method); |
| 1085 |
if (!ctx) |
| 1086 |
return false; |
| 1087 |
Filerange range = (Filerange){ .start = 0, .end = text_size(txt) }; |
| 1088 |
ssize_t written = text_save_write_range(ctx, &range); |
| 1089 |
if (written == -1 || (size_t)written != text_range_size(&range)) { |
| 1090 |
text_save_cancel(ctx); |
| 1091 |
return false; |
| 1092 |
} |
| 1093 |
return text_save_commit(ctx); |
| 1094 |
} |
| 1095 |
|
| 1096 |
ssize_t text_save_write_range(TextSave *ctx, Filerange *range) { |
| 1097 |
return text_write_range(ctx->txt, range, ctx->fd); |
| 1098 |
} |
| 1099 |
|
| 1100 |
ssize_t text_write(Text *txt, int fd) { |
| 1101 |
Filerange r = (Filerange){ .start = 0, .end = text_size(txt) }; |
| 1102 |
return text_write_range(txt, &r, fd); |
| 1103 |
} |
| 1104 |
|
| 1105 |
ssize_t text_write_range(Text *txt, Filerange *range, int fd) { |
| 1106 |
size_t size = text_range_size(range), rem = size; |
| 1107 |
for (Iterator it = text_iterator_get(txt, range->start); |
| 1108 |
rem > 0 && text_iterator_valid(&it); |
| 1109 |
text_iterator_next(&it)) { |
| 1110 |
size_t prem = it.end - it.text; |
| 1111 |
if (prem > rem) |
| 1112 |
prem = rem; |
| 1113 |
ssize_t written = write_all(fd, it.text, prem); |
| 1114 |
if (written == -1) |
| 1115 |
return -1; |
| 1116 |
rem -= written; |
| 1117 |
if ((size_t)written != prem) |
| 1118 |
break; |
| 1119 |
} |
| 1120 |
return size - rem; |
| 1121 |
} |
| 1122 |
|
| 1123 |
Text *text_load(const char *filename) { |
| 1124 |
return text_load_method(filename, TEXT_LOAD_AUTO); |
| 1125 |
} |
| 1126 |
|
| 1127 |
Text *text_load_method(const char *filename, enum TextLoadMethod method) { |
| 1128 |
int fd = -1; |
| 1129 |
size_t size = 0; |
| 1130 |
Text *txt = calloc(1, sizeof *txt); |
589 |
Text *txt = calloc(1, sizeof *txt); |
| 1131 |
if (!txt) |
590 |
if (!txt) |
| 1132 |
return NULL; |
591 |
return NULL; |
| 1133 |
Piece *p = piece_alloc(txt); |
592 |
Piece *p = piece_alloc(txt); |
| 1134 |
if (!p) |
593 |
if (!p) |
| 1135 |
goto out; |
594 |
goto out; |
|
|
595 |
Block *block = NULL; |
| 596 |
array_init(&txt->blocks); |
| 1136 |
lineno_cache_invalidate(&txt->lines); |
597 |
lineno_cache_invalidate(&txt->lines); |
| 1137 |
if (filename) { |
598 |
if (filename) { |
| 1138 |
if ((fd = open(filename, O_RDONLY)) == -1) |
599 |
errno = 0; |
|
|
600 |
block = block_load(dirfd, filename, method, &txt->info); |
| 601 |
if (!block && errno) |
| 1139 |
goto out; |
602 |
goto out; |
| 1140 |
if (fstat(fd, &txt->info) == -1) |
603 |
if (block && !array_add_ptr(&txt->blocks, block)) { |
|
|
604 |
block_free(block); |
| 1141 |
goto out; |
605 |
goto out; |
| 1142 |
if (!S_ISREG(txt->info.st_mode)) { |
|
|
| 1143 |
errno = S_ISDIR(txt->info.st_mode) ? EISDIR : ENOTSUP; |
| 1144 |
goto out; |
| 1145 |
} |
606 |
} |
| 1146 |
// XXX: use lseek(fd, 0, SEEK_END); instead? |
|
|
| 1147 |
size = txt->info.st_size; |
| 1148 |
if (size > 0) { |
| 1149 |
if (method == TEXT_LOAD_READ || (method == TEXT_LOAD_AUTO && size < BLOCK_MMAP_SIZE)) |
| 1150 |
txt->block = block_read(txt, size, fd); |
| 1151 |
else |
| 1152 |
txt->block = block_mmap(txt, size, fd, 0); |
| 1153 |
if (!txt->block) |
| 1154 |
goto out; |
| 1155 |
piece_init(p, &txt->begin, &txt->end, txt->block->data, txt->block->len); |
| 1156 |
} |
| 1157 |
} |
607 |
} |
| 1158 |
|
608 |
|
| 1159 |
if (size == 0) |
609 |
if (!block) |
| 1160 |
piece_init(p, &txt->begin, &txt->end, "\0", 0); |
610 |
piece_init(p, &txt->begin, &txt->end, "\0", 0); |
|
|
611 |
else |
| 612 |
piece_init(p, &txt->begin, &txt->end, block->data, block->len); |
| 1161 |
|
613 |
|
| 1162 |
piece_init(&txt->begin, NULL, p, NULL, 0); |
614 |
piece_init(&txt->begin, NULL, p, NULL, 0); |
| 1163 |
piece_init(&txt->end, p, NULL, NULL, 0); |
615 |
piece_init(&txt->end, p, NULL, NULL, 0); |
|
Lines 1167-1188
Link Here
|
| 1167 |
text_snapshot(txt); |
619 |
text_snapshot(txt); |
| 1168 |
txt->saved_revision = txt->history; |
620 |
txt->saved_revision = txt->history; |
| 1169 |
|
621 |
|
| 1170 |
if (fd != -1) |
|
|
| 1171 |
close(fd); |
| 1172 |
return txt; |
622 |
return txt; |
| 1173 |
out: |
623 |
out: |
| 1174 |
if (fd != -1) |
|
|
| 1175 |
close(fd); |
| 1176 |
text_free(txt); |
624 |
text_free(txt); |
| 1177 |
return NULL; |
625 |
return NULL; |
| 1178 |
} |
626 |
} |
| 1179 |
|
627 |
|
| 1180 |
struct stat text_stat(Text *txt) { |
628 |
struct stat text_stat(const Text *txt) { |
| 1181 |
return txt->info; |
629 |
return txt->info; |
| 1182 |
} |
630 |
} |
| 1183 |
|
631 |
|
|
|
632 |
void text_saved(Text *txt, struct stat *meta) { |
| 633 |
if (meta) |
| 634 |
txt->info = *meta; |
| 635 |
txt->saved_revision = txt->history; |
| 636 |
text_snapshot(txt); |
| 637 |
} |
| 638 |
|
| 639 |
Block *text_block_mmaped(Text *txt) { |
| 640 |
Block *block = array_get_ptr(&txt->blocks, 0); |
| 641 |
if (block && block->type == BLOCK_TYPE_MMAP_ORIG && block->size) |
| 642 |
return block; |
| 643 |
return NULL; |
| 644 |
} |
| 645 |
|
| 1184 |
/* A delete operation can either start/stop midway through a piece or at |
646 |
/* A delete operation can either start/stop midway through a piece or at |
| 1185 |
* a boundry. In the former case a new piece is created to represent the |
647 |
* a boundary. In the former case a new piece is created to represent the |
| 1186 |
* remaining text before/after the modification point. |
648 |
* remaining text before/after the modification point. |
| 1187 |
* |
649 |
* |
| 1188 |
* /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\ |
650 |
* /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\ |
|
Lines 1221-1227
Link Here
|
| 1221 |
size_t cur; /* how much has already been deleted */ |
683 |
size_t cur; /* how much has already been deleted */ |
| 1222 |
|
684 |
|
| 1223 |
if (off == p->len) { |
685 |
if (off == p->len) { |
| 1224 |
/* deletion starts at a piece boundry */ |
686 |
/* deletion starts at a piece boundary */ |
| 1225 |
cur = 0; |
687 |
cur = 0; |
| 1226 |
before = p; |
688 |
before = p; |
| 1227 |
start = p->next; |
689 |
start = p->next; |
|
Lines 1242-1248
Link Here
|
| 1242 |
} |
704 |
} |
| 1243 |
|
705 |
|
| 1244 |
if (cur == len) { |
706 |
if (cur == len) { |
| 1245 |
/* deletion stops at a piece boundry */ |
707 |
/* deletion stops at a piece boundary */ |
| 1246 |
end = p; |
708 |
end = p; |
| 1247 |
after = p->next; |
709 |
after = p->next; |
| 1248 |
} else { |
710 |
} else { |
|
Lines 1278-1284
Link Here
|
| 1278 |
return true; |
740 |
return true; |
| 1279 |
} |
741 |
} |
| 1280 |
|
742 |
|
| 1281 |
bool text_delete_range(Text *txt, Filerange *r) { |
743 |
bool text_delete_range(Text *txt, const Filerange *r) { |
| 1282 |
if (!text_range_valid(r)) |
744 |
if (!text_range_valid(r)) |
| 1283 |
return false; |
745 |
return false; |
| 1284 |
return text_delete(txt, r->start, text_range_size(r)); |
746 |
return text_delete(txt, r->start, text_range_size(r)); |
|
Lines 1286-1296
Link Here
|
| 1286 |
|
748 |
|
| 1287 |
/* preserve the current text content such that it can be restored by |
749 |
/* preserve the current text content such that it can be restored by |
| 1288 |
* means of undo/redo operations */ |
750 |
* means of undo/redo operations */ |
| 1289 |
void text_snapshot(Text *txt) { |
751 |
bool text_snapshot(Text *txt) { |
| 1290 |
if (txt->current_revision) |
752 |
if (txt->current_revision) |
| 1291 |
txt->last_revision = txt->current_revision; |
753 |
txt->last_revision = txt->current_revision; |
| 1292 |
txt->current_revision = NULL; |
754 |
txt->current_revision = NULL; |
| 1293 |
txt->cache = NULL; |
755 |
txt->cache = NULL; |
|
|
756 |
return true; |
| 1294 |
} |
757 |
} |
| 1295 |
|
758 |
|
| 1296 |
|
759 |
|
|
Lines 1313-1577
Link Here
|
| 1313 |
piece_free(p); |
776 |
piece_free(p); |
| 1314 |
} |
777 |
} |
| 1315 |
|
778 |
|
| 1316 |
for (Block *next, *blk = txt->blocks; blk; blk = next) { |
779 |
for (size_t i = 0, len = array_length(&txt->blocks); i < len; i++) |
| 1317 |
next = blk->next; |
780 |
block_free(array_get_ptr(&txt->blocks, i)); |
| 1318 |
block_free(blk); |
781 |
array_release(&txt->blocks); |
| 1319 |
} |
|
|
| 1320 |
|
782 |
|
| 1321 |
free(txt); |
783 |
free(txt); |
| 1322 |
} |
784 |
} |
| 1323 |
|
785 |
|
| 1324 |
bool text_modified(Text *txt) { |
786 |
bool text_modified(const Text *txt) { |
| 1325 |
return txt->saved_revision != txt->history; |
787 |
return txt->saved_revision != txt->history; |
| 1326 |
} |
788 |
} |
| 1327 |
|
789 |
|
| 1328 |
bool text_mmaped(Text *txt, const char *ptr) { |
790 |
bool text_mmaped(const Text *txt, const char *ptr) { |
| 1329 |
uintptr_t addr = (uintptr_t)ptr; |
791 |
uintptr_t addr = (uintptr_t)ptr; |
| 1330 |
for (Block *blk = txt->blocks; blk; blk = blk->next) { |
792 |
for (size_t i = 0, len = array_length(&txt->blocks); i < len; i++) { |
| 1331 |
if ((blk->type == MMAP_ORIG || blk->type == MMAP) && |
793 |
Block *blk = array_get_ptr(&txt->blocks, i); |
|
|
794 |
if ((blk->type == BLOCK_TYPE_MMAP_ORIG || blk->type == BLOCK_TYPE_MMAP) && |
| 1332 |
(uintptr_t)(blk->data) <= addr && addr < (uintptr_t)(blk->data + blk->size)) |
795 |
(uintptr_t)(blk->data) <= addr && addr < (uintptr_t)(blk->data + blk->size)) |
| 1333 |
return true; |
796 |
return true; |
| 1334 |
} |
797 |
} |
| 1335 |
return false; |
798 |
return false; |
| 1336 |
} |
799 |
} |
| 1337 |
|
800 |
|
| 1338 |
static bool text_iterator_init(Iterator *it, size_t pos, Piece *p, size_t off) { |
801 |
static bool iterator_init(Iterator *it, size_t pos, Piece *p, size_t off) { |
| 1339 |
Iterator iter = (Iterator){ |
802 |
*it = (Iterator){ |
| 1340 |
.pos = pos, |
803 |
.pos = pos, |
| 1341 |
.piece = p, |
804 |
.piece = p, |
| 1342 |
.start = p ? p->data : NULL, |
805 |
.start = p ? p->data : NULL, |
| 1343 |
.end = p ? p->data + p->len : NULL, |
806 |
.end = p && p->data ? p->data + p->len : NULL, |
| 1344 |
.text = p ? p->data + off : NULL, |
807 |
.text = p && p->data ? p->data + off : NULL, |
| 1345 |
}; |
808 |
}; |
| 1346 |
*it = iter; |
|
|
| 1347 |
return text_iterator_valid(it); |
809 |
return text_iterator_valid(it); |
| 1348 |
} |
810 |
} |
| 1349 |
|
811 |
|
| 1350 |
Iterator text_iterator_get(Text *txt, size_t pos) { |
812 |
bool text_iterator_init(const Text *txt, Iterator *it, size_t pos) { |
| 1351 |
Iterator it; |
|
|
| 1352 |
Location loc = piece_get_extern(txt, pos); |
813 |
Location loc = piece_get_extern(txt, pos); |
| 1353 |
text_iterator_init(&it, pos, loc.piece, loc.off); |
814 |
return iterator_init(it, pos, loc.piece, loc.off); |
| 1354 |
return it; |
|
|
| 1355 |
} |
815 |
} |
| 1356 |
|
816 |
|
| 1357 |
bool text_iterator_byte_get(Iterator *it, char *b) { |
817 |
Iterator text_iterator_get(const Text *txt, size_t pos) { |
| 1358 |
if (text_iterator_valid(it)) { |
818 |
Iterator it; |
| 1359 |
if (it->start <= it->text && it->text < it->end) { |
819 |
text_iterator_init(txt, &it, pos); |
| 1360 |
*b = *it->text; |
820 |
return it; |
| 1361 |
return true; |
|
|
| 1362 |
} else if (it->pos == it->piece->text->size) { /* EOF */ |
| 1363 |
*b = '\0'; |
| 1364 |
return true; |
| 1365 |
} |
| 1366 |
} |
| 1367 |
return false; |
| 1368 |
} |
821 |
} |
| 1369 |
|
822 |
|
| 1370 |
bool text_iterator_next(Iterator *it) { |
823 |
bool text_iterator_next(Iterator *it) { |
| 1371 |
size_t rem = it->end - it->text; |
824 |
size_t rem = it->end - it->text; |
| 1372 |
return text_iterator_init(it, it->pos+rem, it->piece ? it->piece->next : NULL, 0); |
825 |
return iterator_init(it, it->pos+rem, it->piece ? it->piece->next : NULL, 0); |
| 1373 |
} |
826 |
} |
| 1374 |
|
827 |
|
| 1375 |
bool text_iterator_prev(Iterator *it) { |
828 |
bool text_iterator_prev(Iterator *it) { |
| 1376 |
size_t off = it->text - it->start; |
829 |
size_t off = it->text - it->start; |
| 1377 |
size_t len = it->piece && it->piece->prev ? it->piece->prev->len : 0; |
830 |
size_t len = it->piece && it->piece->prev ? it->piece->prev->len : 0; |
| 1378 |
return text_iterator_init(it, it->pos-off, it->piece ? it->piece->prev : NULL, len); |
831 |
return iterator_init(it, it->pos-off, it->piece ? it->piece->prev : NULL, len); |
| 1379 |
} |
832 |
} |
| 1380 |
|
833 |
|
|
|
834 |
const Text *text_iterator_text(const Iterator *it) { |
| 835 |
return it->piece ? it->piece->text : NULL; |
| 836 |
} |
| 837 |
|
| 1381 |
bool text_iterator_valid(const Iterator *it) { |
838 |
bool text_iterator_valid(const Iterator *it) { |
| 1382 |
/* filter out sentinel nodes */ |
839 |
/* filter out sentinel nodes */ |
| 1383 |
return it->piece && it->piece->text; |
840 |
return it->piece && it->piece->text; |
| 1384 |
} |
841 |
} |
| 1385 |
|
842 |
|
| 1386 |
bool text_iterator_byte_next(Iterator *it, char *b) { |
843 |
bool text_iterator_has_next(const Iterator *it) { |
| 1387 |
if (!it->piece || !it->piece->next) |
844 |
return it->piece && it->piece->next; |
| 1388 |
return false; |
|
|
| 1389 |
bool eof = true; |
| 1390 |
if (it->text < it->end) { |
| 1391 |
it->text++; |
| 1392 |
it->pos++; |
| 1393 |
eof = false; |
| 1394 |
} else if (!it->piece->prev) { |
| 1395 |
eof = false; |
| 1396 |
} |
| 1397 |
|
| 1398 |
while (it->text == it->end) { |
| 1399 |
if (!text_iterator_next(it)) { |
| 1400 |
if (eof) |
| 1401 |
return false; |
| 1402 |
if (b) |
| 1403 |
*b = '\0'; |
| 1404 |
return text_iterator_prev(it); |
| 1405 |
} |
| 1406 |
} |
| 1407 |
|
| 1408 |
if (b) |
| 1409 |
*b = *it->text; |
| 1410 |
return true; |
| 1411 |
} |
845 |
} |
| 1412 |
|
846 |
|
| 1413 |
bool text_iterator_byte_prev(Iterator *it, char *b) { |
847 |
bool text_iterator_has_prev(const Iterator *it) { |
| 1414 |
if (!it->piece || !it->piece->prev) |
848 |
return it->piece && it->piece->prev; |
| 1415 |
return false; |
|
|
| 1416 |
bool eof = !it->piece->next; |
| 1417 |
while (it->text == it->start) { |
| 1418 |
if (!text_iterator_prev(it)) { |
| 1419 |
if (!eof) |
| 1420 |
return false; |
| 1421 |
if (b) |
| 1422 |
*b = '\0'; |
| 1423 |
return text_iterator_next(it); |
| 1424 |
} |
| 1425 |
} |
| 1426 |
|
| 1427 |
--it->text; |
| 1428 |
--it->pos; |
| 1429 |
|
| 1430 |
if (b) |
| 1431 |
*b = *it->text; |
| 1432 |
return true; |
| 1433 |
} |
849 |
} |
| 1434 |
|
850 |
|
| 1435 |
bool text_iterator_byte_find_prev(Iterator *it, char b) { |
851 |
size_t text_size(const Text *txt) { |
| 1436 |
while (it->text) { |
|
|
| 1437 |
const char *match = memrchr(it->start, b, it->text - it->start); |
| 1438 |
if (match) { |
| 1439 |
it->pos -= it->text - match; |
| 1440 |
it->text = match; |
| 1441 |
return true; |
| 1442 |
} |
| 1443 |
text_iterator_prev(it); |
| 1444 |
} |
| 1445 |
text_iterator_next(it); |
| 1446 |
return false; |
| 1447 |
} |
| 1448 |
|
| 1449 |
bool text_iterator_byte_find_next(Iterator *it, char b) { |
| 1450 |
while (it->text) { |
| 1451 |
const char *match = memchr(it->text, b, it->end - it->text); |
| 1452 |
if (match) { |
| 1453 |
it->pos += match - it->text; |
| 1454 |
it->text = match; |
| 1455 |
return true; |
| 1456 |
} |
| 1457 |
text_iterator_next(it); |
| 1458 |
} |
| 1459 |
text_iterator_prev(it); |
| 1460 |
return false; |
| 1461 |
} |
| 1462 |
|
| 1463 |
bool text_iterator_codepoint_next(Iterator *it, char *c) { |
| 1464 |
while (text_iterator_byte_next(it, NULL)) { |
| 1465 |
if (ISUTF8(*it->text)) { |
| 1466 |
if (c) |
| 1467 |
*c = *it->text; |
| 1468 |
return true; |
| 1469 |
} |
| 1470 |
} |
| 1471 |
return false; |
| 1472 |
} |
| 1473 |
|
| 1474 |
bool text_iterator_codepoint_prev(Iterator *it, char *c) { |
| 1475 |
while (text_iterator_byte_prev(it, NULL)) { |
| 1476 |
if (ISUTF8(*it->text)) { |
| 1477 |
if (c) |
| 1478 |
*c = *it->text; |
| 1479 |
return true; |
| 1480 |
} |
| 1481 |
} |
| 1482 |
return false; |
| 1483 |
} |
| 1484 |
|
| 1485 |
bool text_iterator_char_next(Iterator *it, char *c) { |
| 1486 |
if (!text_iterator_codepoint_next(it, c)) |
| 1487 |
return false; |
| 1488 |
mbstate_t ps = { 0 }; |
| 1489 |
for (;;) { |
| 1490 |
char buf[MB_LEN_MAX]; |
| 1491 |
size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf); |
| 1492 |
wchar_t wc; |
| 1493 |
size_t wclen = mbrtowc(&wc, buf, len, &ps); |
| 1494 |
if (wclen == (size_t)-1 && errno == EILSEQ) { |
| 1495 |
return true; |
| 1496 |
} else if (wclen == (size_t)-2) { |
| 1497 |
return false; |
| 1498 |
} else if (wclen == 0) { |
| 1499 |
return true; |
| 1500 |
} else { |
| 1501 |
int width = wcwidth(wc); |
| 1502 |
if (width != 0) |
| 1503 |
return true; |
| 1504 |
if (!text_iterator_codepoint_next(it, c)) |
| 1505 |
return false; |
| 1506 |
} |
| 1507 |
} |
| 1508 |
return true; |
| 1509 |
} |
| 1510 |
|
| 1511 |
bool text_iterator_char_prev(Iterator *it, char *c) { |
| 1512 |
if (!text_iterator_codepoint_prev(it, c)) |
| 1513 |
return false; |
| 1514 |
for (;;) { |
| 1515 |
char buf[MB_LEN_MAX]; |
| 1516 |
size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf); |
| 1517 |
wchar_t wc; |
| 1518 |
mbstate_t ps = { 0 }; |
| 1519 |
size_t wclen = mbrtowc(&wc, buf, len, &ps); |
| 1520 |
if (wclen == (size_t)-1 && errno == EILSEQ) { |
| 1521 |
return true; |
| 1522 |
} else if (wclen == (size_t)-2) { |
| 1523 |
return false; |
| 1524 |
} else if (wclen == 0) { |
| 1525 |
return true; |
| 1526 |
} else { |
| 1527 |
int width = wcwidth(wc); |
| 1528 |
if (width != 0) |
| 1529 |
return true; |
| 1530 |
if (!text_iterator_codepoint_prev(it, c)) |
| 1531 |
return false; |
| 1532 |
} |
| 1533 |
} |
| 1534 |
return true; |
| 1535 |
} |
| 1536 |
|
| 1537 |
bool text_byte_get(Text *txt, size_t pos, char *byte) { |
| 1538 |
return text_bytes_get(txt, pos, 1, byte); |
| 1539 |
} |
| 1540 |
|
| 1541 |
size_t text_bytes_get(Text *txt, size_t pos, size_t len, char *buf) { |
| 1542 |
if (!buf) |
| 1543 |
return 0; |
| 1544 |
char *cur = buf; |
| 1545 |
size_t rem = len; |
| 1546 |
for (Iterator it = text_iterator_get(txt, pos); |
| 1547 |
text_iterator_valid(&it); |
| 1548 |
text_iterator_next(&it)) { |
| 1549 |
if (rem == 0) |
| 1550 |
break; |
| 1551 |
size_t piece_len = it.end - it.text; |
| 1552 |
if (piece_len > rem) |
| 1553 |
piece_len = rem; |
| 1554 |
if (piece_len) { |
| 1555 |
memcpy(cur, it.text, piece_len); |
| 1556 |
cur += piece_len; |
| 1557 |
rem -= piece_len; |
| 1558 |
} |
| 1559 |
} |
| 1560 |
return len - rem; |
| 1561 |
} |
| 1562 |
|
| 1563 |
char *text_bytes_alloc0(Text *txt, size_t pos, size_t len) { |
| 1564 |
if (len == SIZE_MAX) |
| 1565 |
return NULL; |
| 1566 |
char *buf = malloc(len+1); |
| 1567 |
if (!buf) |
| 1568 |
return NULL; |
| 1569 |
len = text_bytes_get(txt, pos, len, buf); |
| 1570 |
buf[len] = '\0'; |
| 1571 |
return buf; |
| 1572 |
} |
| 1573 |
|
| 1574 |
size_t text_size(Text *txt) { |
| 1575 |
return txt->size; |
852 |
return txt->size; |
| 1576 |
} |
853 |
} |
| 1577 |
|
854 |
|
|
Lines 1680-1686
Link Here
|
| 1680 |
return (Mark)(loc.piece->data + loc.off); |
957 |
return (Mark)(loc.piece->data + loc.off); |
| 1681 |
} |
958 |
} |
| 1682 |
|
959 |
|
| 1683 |
size_t text_mark_get(Text *txt, Mark mark) { |
960 |
size_t text_mark_get(const Text *txt, Mark mark) { |
| 1684 |
size_t cur = 0; |
961 |
size_t cur = 0; |
| 1685 |
|
962 |
|
| 1686 |
if (mark == EMARK) |
963 |
if (mark == EMARK) |