2 * Copyright (c) 2014-2015 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 #include <sys/types.h>
32 #include <selinux/selinux.h>
36 #include "text-util.h"
39 /* Allocate buffers holding the actual file content in junks of size: */
40 #define BUFFER_SIZE (1 << 20)
41 /* Files smaller than this value are copied on load, larger ones are mmap(2)-ed
42 * directely. Hence the former can be truncated, while doing so on the latter
43 * results in havoc. */
44 #define BUFFER_MMAP_SIZE (1 << 23)
46 /* Buffer holding the file content, either readonly mmap(2)-ed from the original
47 * file or heap allocated to store the modifications.
49 typedef struct Buffer Buffer
;
51 size_t size
; /* maximal capacity */
52 size_t len
; /* current used length / insertion position */
53 char *data
; /* actual data */
54 enum { MMAP
, MALLOC
} type
; /* type of allocation */
55 Buffer
*next
; /* next junk */
58 /* A piece holds a reference (but doesn't itself store) a certain amount of data.
59 * All active pieces chained together form the whole content of the document.
60 * At the beginning there exists only one piece, spanning the whole document.
61 * Upon insertion/deletion new pieces will be created to represent the changes.
62 * Generally pieces are never destroyed, but kept around to peform undo/redo
66 Text
*text
; /* text to which this piece belongs */
67 Piece
*prev
, *next
; /* pointers to the logical predecessor/successor */
68 Piece
*global_prev
; /* double linked list in order of allocation, */
69 Piece
*global_next
; /* used to free individual pieces */
70 const char *data
; /* pointer into a Buffer holding the data */
71 size_t len
; /* the length in number of bytes of the data */
74 /* used to transform a global position (byte offset starting from the beginning
75 * of the text) into an offset relative to a piece.
78 Piece
*piece
; /* piece holding the location */
79 size_t off
; /* offset into the piece in bytes */
82 /* A Span holds a certain range of pieces. Changes to the document are always
83 * performed by swapping out an existing span with a new one.
86 Piece
*start
, *end
; /* start/end of the span */
87 size_t len
; /* the sum of the lengths of the pieces which form this span */
90 /* A Change keeps all needed information to redo/undo an insertion/deletion. */
91 typedef struct Change Change
;
93 Span old
; /* all pieces which are being modified/swapped out by the change */
94 Span
new; /* all pieces which are introduced/swapped in by the change */
95 size_t pos
; /* absolute position at which the change occured */
96 Change
*next
; /* next change which is part of the same action */
97 Change
*prev
; /* previous change which is part of the same action */
100 /* An Action is a list of Changes which are used to undo/redo all modifications
101 * since the last snapshot operation. Actions are stored in a directed graph structure.
103 typedef struct Action Action
;
105 Change
*change
; /* the most recent change */
106 Action
*next
; /* the next (child) action in the undo tree */
107 Action
*prev
; /* the previous (parent) operation in the undo tree */
108 Action
*earlier
; /* the previous Action, chronologically */
109 Action
*later
; /* the next Action, chronologically */
110 time_t time
; /* when the first change of this action was performed */
111 size_t seq
; /* a unique, strictly increasing identifier */
115 size_t pos
; /* position in bytes from start of file */
116 size_t lineno
; /* line number in file i.e. number of '\n' in [0, pos) */
119 /* The main struct holding all information of a given file */
121 Buffer
*buf
; /* original file content at the time of load operation */
122 Buffer
*buffers
; /* all buffers which have been allocated to hold insertion data */
123 Piece
*pieces
; /* all pieces which have been allocated, used to free them */
124 Piece
*cache
; /* most recently modified piece */
125 Piece begin
, end
; /* sentinel nodes which always exists but don't hold any data */
126 Action
*history
; /* undo tree */
127 Action
*current_action
; /* action holding all file changes until a snapshot is performed */
128 Action
*last_action
; /* the last action added to the tree, chronologically */
129 Action
*saved_action
; /* the last action at the time of the save operation */
130 size_t size
; /* current file content size in bytes */
131 struct stat info
; /* stat as probed at load time */
132 LineCache lines
; /* mapping between absolute pos in bytes and logical line breaks */
133 enum TextNewLine newlines
; /* which type of new lines does the file use */
136 /* buffer management */
137 static Buffer
*buffer_alloc(Text
*txt
, size_t size
);
138 static Buffer
*buffer_read(Text
*txt
, size_t size
, int fd
);
139 static Buffer
*buffer_mmap(Text
*txt
, size_t size
, int fd
, off_t offset
);
140 static void buffer_free(Buffer
*buf
);
141 static bool buffer_capacity(Buffer
*buf
, size_t len
);
142 static const char *buffer_append(Buffer
*buf
, const char *data
, size_t len
);
143 static bool buffer_insert(Buffer
*buf
, size_t pos
, const char *data
, size_t len
);
144 static bool buffer_delete(Buffer
*buf
, size_t pos
, size_t len
);
145 static const char *buffer_store(Text
*txt
, const char *data
, size_t len
);
147 static void cache_piece(Text
*txt
, Piece
*p
);
148 static bool cache_contains(Text
*txt
, Piece
*p
);
149 static bool cache_insert(Text
*txt
, Piece
*p
, size_t off
, const char *data
, size_t len
);
150 static bool cache_delete(Text
*txt
, Piece
*p
, size_t off
, size_t len
);
151 /* piece management */
152 static Piece
*piece_alloc(Text
*txt
);
153 static void piece_free(Piece
*p
);
154 static void piece_init(Piece
*p
, Piece
*prev
, Piece
*next
, const char *data
, size_t len
);
155 static Location
piece_get_intern(Text
*txt
, size_t pos
);
156 static Location
piece_get_extern(Text
*txt
, size_t pos
);
157 /* span management */
158 static void span_init(Span
*span
, Piece
*start
, Piece
*end
);
159 static void span_swap(Text
*txt
, Span
*old
, Span
*new);
160 /* change management */
161 static Change
*change_alloc(Text
*txt
, size_t pos
);
162 static void change_free(Change
*c
);
163 /* action management */
164 static Action
*action_alloc(Text
*txt
);
165 static void action_free(Action
*a
);
166 /* logical line counting cache */
167 static void lineno_cache_invalidate(LineCache
*cache
);
168 static size_t lines_skip_forward(Text
*txt
, size_t pos
, size_t lines
, size_t *lines_skiped
);
169 static size_t lines_count(Text
*txt
, size_t pos
, size_t len
);
171 static ssize_t
write_all(int fd
, const char *buf
, size_t count
) {
174 ssize_t written
= write(fd
, buf
, rem
);
176 if (errno
== EAGAIN
|| errno
== EINTR
)
179 } else if (written
== 0) {
188 /* allocate a new buffer of MAX(size, BUFFER_SIZE) bytes */
189 static Buffer
*buffer_alloc(Text
*txt
, size_t size
) {
190 Buffer
*buf
= calloc(1, sizeof(Buffer
));
193 if (BUFFER_SIZE
> size
)
195 if (!(buf
->data
= malloc(size
))) {
201 buf
->next
= txt
->buffers
;
206 static Buffer
*buffer_read(Text
*txt
, size_t size
, int fd
) {
207 Buffer
*buf
= buffer_alloc(txt
, size
);
212 ssize_t len
= read(fd
, data
, MIN(sizeof(data
), size
));
214 txt
->buffers
= buf
->next
;
217 } else if (len
== 0) {
220 buffer_append(buf
, data
, len
);
227 static Buffer
*buffer_mmap(Text
*txt
, size_t size
, int fd
, off_t offset
) {
228 Buffer
*buf
= calloc(1, sizeof(Buffer
));
232 buf
->data
= mmap(NULL
, size
, PROT_READ
, MAP_SHARED
, fd
, offset
);
233 if (buf
->data
== MAP_FAILED
) {
241 buf
->next
= txt
->buffers
;
246 static void buffer_free(Buffer
*buf
) {
249 if (buf
->type
== MALLOC
)
251 else if (buf
->type
== MMAP
&& buf
->data
)
252 munmap(buf
->data
, buf
->size
);
256 /* check whether buffer has enough free space to store len bytes */
257 static bool buffer_capacity(Buffer
*buf
, size_t len
) {
258 return buf
->size
- buf
->len
>= len
;
261 /* append data to buffer, assumes there is enough space available */
262 static const char *buffer_append(Buffer
*buf
, const char *data
, size_t len
) {
263 char *dest
= memcpy(buf
->data
+ buf
->len
, data
, len
);
268 /* stores the given data in a buffer, allocates a new one if necessary. returns
269 * a pointer to the storage location or NULL if allocation failed. */
270 static const char *buffer_store(Text
*txt
, const char *data
, size_t len
) {
271 Buffer
*buf
= txt
->buffers
;
272 if ((!buf
|| !buffer_capacity(buf
, len
)) && !(buf
= buffer_alloc(txt
, len
)))
274 return buffer_append(buf
, data
, len
);
277 /* insert data into buffer at an arbitrary position, this should only be used with
278 * data of the most recently created piece. */
279 static bool buffer_insert(Buffer
*buf
, size_t pos
, const char *data
, size_t len
) {
280 if (pos
> buf
->len
|| !buffer_capacity(buf
, len
))
283 return buffer_append(buf
, data
, len
);
284 char *insert
= buf
->data
+ pos
;
285 memmove(insert
+ len
, insert
, buf
->len
- pos
);
286 memcpy(insert
, data
, len
);
291 /* delete data from a buffer at an arbitrary position, this should only be used with
292 * data of the most recently created piece. */
293 static bool buffer_delete(Buffer
*buf
, size_t pos
, size_t len
) {
294 if (pos
+ len
> buf
->len
)
296 if (buf
->len
== pos
) {
300 char *delete = buf
->data
+ pos
;
301 memmove(delete, delete + len
, buf
->len
- pos
- len
);
306 /* cache the given piece if it is the most recently changed one */
307 static void cache_piece(Text
*txt
, Piece
*p
) {
308 Buffer
*buf
= txt
->buffers
;
309 if (!buf
|| p
->data
< buf
->data
|| p
->data
+ p
->len
!= buf
->data
+ buf
->len
)
314 /* check whether the given piece was the most recently modified one */
315 static bool cache_contains(Text
*txt
, Piece
*p
) {
316 Buffer
*buf
= txt
->buffers
;
317 Action
*a
= txt
->current_action
;
318 if (!buf
|| !txt
->cache
|| txt
->cache
!= p
|| !a
|| !a
->change
)
321 Piece
*start
= a
->change
->new.start
;
322 Piece
*end
= a
->change
->new.end
;
324 for (Piece
*cur
= start
; !found
; cur
= cur
->next
) {
331 return found
&& p
->data
+ p
->len
== buf
->data
+ buf
->len
;
334 /* try to insert a junk of data at a given piece offset. the insertion is only
335 * performed if the piece is the most recenetly changed one. the legnth of the
336 * piece, the span containing it and the whole text is adjusted accordingly */
337 static bool cache_insert(Text
*txt
, Piece
*p
, size_t off
, const char *data
, size_t len
) {
338 if (!cache_contains(txt
, p
))
340 Buffer
*buf
= txt
->buffers
;
341 size_t bufpos
= p
->data
+ off
- buf
->data
;
342 if (!buffer_insert(buf
, bufpos
, data
, len
))
345 txt
->current_action
->change
->new.len
+= len
;
350 /* try to delete a junk of data at a given piece offset. the deletion is only
351 * performed if the piece is the most recenetly changed one and the whole
352 * affected range lies within it. the legnth of the piece, the span containing it
353 * and the whole text is adjusted accordingly */
354 static bool cache_delete(Text
*txt
, Piece
*p
, size_t off
, size_t len
) {
355 if (!cache_contains(txt
, p
))
357 Buffer
*buf
= txt
->buffers
;
358 size_t bufpos
= p
->data
+ off
- buf
->data
;
359 if (off
+ len
> p
->len
|| !buffer_delete(buf
, bufpos
, len
))
362 txt
->current_action
->change
->new.len
-= len
;
367 /* initialize a span and calculate its length */
368 static void span_init(Span
*span
, Piece
*start
, Piece
*end
) {
372 for (Piece
*p
= start
; p
; p
= p
->next
) {
380 /* swap out an old span and replace it with a new one.
382 * - if old is an empty span do not remove anything, just insert the new one
383 * - if new is an empty span do not insert anything, just remove the old one
385 * adjusts the document size accordingly.
387 static void span_swap(Text
*txt
, Span
*old
, Span
*new) {
388 if (old
->len
== 0 && new->len
== 0) {
390 } else if (old
->len
== 0) {
391 /* insert new span */
392 new->start
->prev
->next
= new->start
;
393 new->end
->next
->prev
= new->end
;
394 } else if (new->len
== 0) {
395 /* delete old span */
396 old
->start
->prev
->next
= old
->end
->next
;
397 old
->end
->next
->prev
= old
->start
->prev
;
399 /* replace old with new */
400 old
->start
->prev
->next
= new->start
;
401 old
->end
->next
->prev
= new->end
;
403 txt
->size
-= old
->len
;
404 txt
->size
+= new->len
;
407 /* allocate a new action, set its pointers to the other actions in the history,
408 * and set it as txt->history. All further changes will be associated with this action. */
409 static Action
*action_alloc(Text
*txt
) {
410 Action
*new = calloc(1, sizeof(Action
));
413 new->time
= time(NULL
);
414 txt
->current_action
= new;
416 /* set sequence number */
417 if (!txt
->last_action
)
420 new->seq
= txt
->last_action
->seq
+ 1;
422 /* set earlier, later pointers */
423 if (txt
->last_action
)
424 txt
->last_action
->later
= new;
425 new->earlier
= txt
->last_action
;
432 /* set prev, next pointers */
433 new->prev
= txt
->history
;
434 txt
->history
->next
= new;
439 static void action_free(Action
*a
) {
442 for (Change
*next
, *c
= a
->change
; c
; c
= next
) {
449 static Piece
*piece_alloc(Text
*txt
) {
450 Piece
*p
= calloc(1, sizeof(Piece
));
454 p
->global_next
= txt
->pieces
;
456 txt
->pieces
->global_prev
= p
;
461 static void piece_free(Piece
*p
) {
465 p
->global_prev
->global_next
= p
->global_next
;
467 p
->global_next
->global_prev
= p
->global_prev
;
468 if (p
->text
->pieces
== p
)
469 p
->text
->pieces
= p
->global_next
;
470 if (p
->text
->cache
== p
)
471 p
->text
->cache
= NULL
;
475 static void piece_init(Piece
*p
, Piece
*prev
, Piece
*next
, const char *data
, size_t len
) {
482 /* returns the piece holding the text at byte offset pos. if pos happens to
483 * be at a piece boundry i.e. the first byte of a piece then the previous piece
484 * to the left is returned with an offset of piece->len. this is convenient for
485 * modifications to the piece chain where both pieces (the returned one and the
486 * one following it) are needed, but unsuitable as a public interface.
488 * in particular if pos is zero, the begin sentinel piece is returned.
490 static Location
piece_get_intern(Text
*txt
, size_t pos
) {
492 for (Piece
*p
= &txt
->begin
; p
->next
; p
= p
->next
) {
493 if (cur
<= pos
&& pos
<= cur
+ p
->len
)
494 return (Location
){ .piece
= p
, .off
= pos
- cur
};
498 return (Location
){ 0 };
501 /* similiar to piece_get_intern but usable as a public API. returns the piece
502 * holding the text at byte offset pos. never returns a sentinel piece.
503 * it pos is the end of file (== text_size()) and the file is not empty then
504 * the last piece holding data is returned.
506 static Location
piece_get_extern(Text
*txt
, size_t pos
) {
509 if (pos
> 0 && pos
== txt
->size
) {
510 Piece
*p
= txt
->begin
.next
;
511 while (p
->next
->next
)
513 return (Location
){ .piece
= p
, .off
= p
->len
};
516 for (Piece
*p
= txt
->begin
.next
; p
->next
; p
= p
->next
) {
517 if (cur
<= pos
&& pos
< cur
+ p
->len
)
518 return (Location
){ .piece
= p
, .off
= pos
- cur
};
522 return (Location
){ 0 };
525 /* allocate a new change, associate it with current action or a newly
526 * allocated one if none exists. */
527 static Change
*change_alloc(Text
*txt
, size_t pos
) {
528 Action
*a
= txt
->current_action
;
530 a
= action_alloc(txt
);
534 Change
*c
= calloc(1, sizeof(Change
));
545 static void change_free(Change
*c
) {
548 /* only free the new part of the span, the old one is still in use */
549 piece_free(c
->new.start
);
550 if (c
->new.start
!= c
->new.end
)
551 piece_free(c
->new.end
);
555 /* When inserting new data there are 2 cases to consider.
557 * - in the first the insertion point falls into the middle of an exisiting
558 * piece which is replaced by three new pieces:
560 * /-+ --> +---------------+ --> +-\
561 * | | | existing text | | |
562 * \-+ <-- +---------------+ <-- +-/
564 * Insertion point for "demo "
566 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
567 * | | | existing| |demo | |text | | |
568 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
570 * - the second case deals with an insertion point at a piece boundry:
572 * /-+ --> +---------------+ --> +-\
573 * | | | existing text | | |
574 * \-+ <-- +---------------+ <-- +-/
576 * Insertion point for "short"
578 * /-+ --> +-----+ --> +---------------+ --> +-\
579 * | | |short| | existing text | | |
580 * \-+ <-- +-----+ <-- +---------------+ <-- +-/
582 bool text_insert(Text
*txt
, size_t pos
, const char *data
, size_t len
) {
587 if (pos
< txt
->lines
.pos
)
588 lineno_cache_invalidate(&txt
->lines
);
590 Location loc
= piece_get_intern(txt
, pos
);
591 Piece
*p
= loc
.piece
;
594 size_t off
= loc
.off
;
595 if (cache_insert(txt
, p
, off
, data
, len
))
598 Change
*c
= change_alloc(txt
, pos
);
602 if (!(data
= buffer_store(txt
, data
, len
)))
608 /* insert between two existing pieces, hence there is nothing to
609 * remove, just add a new piece holding the extra text */
610 if (!(new = piece_alloc(txt
)))
612 piece_init(new, p
, p
->next
, data
, len
);
613 span_init(&c
->new, new, new);
614 span_init(&c
->old
, NULL
, NULL
);
616 /* insert into middle of an existing piece, therfore split the old
617 * piece. that is we have 3 new pieces one containing the content
618 * before the insertion point then one holding the newly inserted
619 * text and one holding the content after the insertion point.
621 Piece
*before
= piece_alloc(txt
);
622 new = piece_alloc(txt
);
623 Piece
*after
= piece_alloc(txt
);
624 if (!before
|| !new || !after
)
626 piece_init(before
, p
->prev
, new, p
->data
, off
);
627 piece_init(new, before
, after
, data
, len
);
628 piece_init(after
, new, p
->next
, p
->data
+ off
, p
->len
- off
);
630 span_init(&c
->new, before
, after
);
631 span_init(&c
->old
, p
, p
);
634 cache_piece(txt
, new);
635 span_swap(txt
, &c
->old
, &c
->new);
639 bool text_appendf(Text
*txt
, const char *format
, ...) {
641 va_start(ap
, format
);
642 bool ret
= text_vprintf(txt
, text_size(txt
), format
, ap
);
647 bool text_printf(Text
*txt
, size_t pos
, const char *format
, ...) {
649 va_start(ap
, format
);
650 bool ret
= text_vprintf(txt
, pos
, format
, ap
);
655 bool text_vprintf(Text
*txt
, size_t pos
, const char *format
, va_list ap
) {
657 va_copy(ap_save
, ap
);
658 int len
= vsnprintf(NULL
, 0, format
, ap
);
661 char *buf
= malloc(len
+1);
662 bool ret
= buf
&& (vsnprintf(buf
, len
+1, format
, ap_save
) == len
) && text_insert(txt
, pos
, buf
, len
);
668 size_t text_insert_newline(Text
*txt
, size_t pos
) {
669 const char *data
= text_newline_char(txt
);
670 size_t len
= strlen(data
);
671 return text_insert(txt
, pos
, data
, len
) ? len
: 0;
674 static size_t action_undo(Text
*txt
, Action
*a
) {
676 for (Change
*c
= a
->change
; c
; c
= c
->next
) {
677 span_swap(txt
, &c
->new, &c
->old
);
683 static size_t action_redo(Text
*txt
, Action
*a
) {
685 Change
*c
= a
->change
;
688 for ( ; c
; c
= c
->prev
) {
689 span_swap(txt
, &c
->old
, &c
->new);
691 if (c
->new.len
> c
->old
.len
)
692 pos
+= c
->new.len
- c
->old
.len
;
697 size_t text_undo(Text
*txt
) {
699 /* taking a snapshot makes sure that txt->current_action is reset */
701 Action
*a
= txt
->history
->prev
;
704 pos
= action_undo(txt
, txt
->history
);
706 lineno_cache_invalidate(&txt
->lines
);
710 size_t text_redo(Text
*txt
) {
712 /* taking a snapshot makes sure that txt->current_action is reset */
714 Action
*a
= txt
->history
->next
;
717 pos
= action_redo(txt
, a
);
719 lineno_cache_invalidate(&txt
->lines
);
723 static bool history_change_branch(Action
*a
) {
724 bool changed
= false;
726 if (a
->prev
->next
!= a
) {
735 static size_t history_traverse_to(Text
*txt
, Action
*a
) {
739 bool changed
= history_change_branch(a
);
741 if (a
->seq
== txt
->history
->seq
) {
742 return txt
->lines
.pos
;
743 } else if (a
->seq
> txt
->history
->seq
) {
744 while (txt
->history
!= a
)
745 pos
= text_redo(txt
);
747 } else if (a
->seq
< txt
->history
->seq
) {
748 while (txt
->history
!= a
)
749 pos
= text_undo(txt
);
753 while (txt
->history
->prev
&& txt
->history
->prev
->next
== txt
->history
)
755 pos
= text_undo(txt
);
756 while (txt
->history
!= a
)
757 pos
= text_redo(txt
);
763 size_t text_earlier(Text
*txt
, int count
) {
764 Action
*a
= txt
->history
;
765 while (count
-- > 0 && a
->earlier
)
767 return history_traverse_to(txt
, a
);
770 size_t text_later(Text
*txt
, int count
) {
771 Action
*a
= txt
->history
;
772 while (count
-- > 0 && a
->later
)
774 return history_traverse_to(txt
, a
);
777 size_t text_restore(Text
*txt
, time_t time
) {
778 Action
*a
= txt
->history
;
779 while (time
< a
->time
&& a
->earlier
)
781 while (time
> a
->time
&& a
->later
)
783 time_t diff
= labs(a
->time
- time
);
784 if (a
->earlier
&& a
->earlier
!= txt
->history
&& labs(a
->earlier
->time
- time
) < diff
)
786 if (a
->later
&& a
->later
!= txt
->history
&& labs(a
->later
->time
- time
) < diff
)
788 return history_traverse_to(txt
, a
);
791 time_t text_state(Text
*txt
) {
792 return txt
->history
->time
;
795 static bool preserve_acl(int src
, int dest
) {
797 acl_t acl
= acl_get_fd(src
);
799 return errno
== ENOTSUP
? true : false;
800 if (acl_set_fd(dest
, acl
) == -1) {
805 #endif /* CONFIG_ACL */
809 static bool preserve_selinux_context(int src
, int dest
) {
811 char *context
= NULL
;
812 if (!is_selinux_enabled())
814 if (fgetfilecon(src
, &context
) == -1)
815 return errno
== ENOTSUP
? true : false;
816 if (fsetfilecon(dest
, context
) == -1) {
821 #endif /* CONFIG_SELINUX */
825 /* Save current content to given filename. The data is first saved to `filename~`
826 * and then atomically moved to its final (possibly alredy existing) destination
827 * using rename(2). This approach does not work if:
829 * - the file is a symbolic link
830 * - the file is a hard link
831 * - file ownership can not be preserved
832 * - file group can not be preserved
833 * - directory permissions do not allow creation of a new file
834 * - POSXI ACL can not be preserved (if enabled)
835 * - SELinux security context can not be preserved (if enabled)
837 static bool text_save_atomic_range(Text
*txt
, Filerange
*range
, const char *filename
) {
838 struct stat meta
= { 0 }, oldmeta
= { 0 };
839 int fd
= -1, oldfd
= -1, saved_errno
;
840 char *tmpname
= NULL
;
841 size_t size
= text_range_size(range
);
842 size_t namelen
= strlen(filename
) + 1 /* ~ */ + 1 /* \0 */;
844 if ((oldfd
= open(filename
, O_RDONLY
)) == -1 && errno
!= ENOENT
)
846 if (oldfd
!= -1 && lstat(filename
, &oldmeta
) == -1)
849 if (S_ISLNK(oldmeta
.st_mode
)) /* symbolic link */
851 if (oldmeta
.st_nlink
> 1) /* hard link */
854 if (!(tmpname
= calloc(1, namelen
)))
856 snprintf(tmpname
, namelen
, "%s~", filename
);
858 /* O_RDWR is needed because otherwise we can't map with MAP_SHARED */
859 if ((fd
= open(tmpname
, O_CREAT
|O_RDWR
|O_TRUNC
, oldfd
== -1 ? S_IRUSR
|S_IWUSR
: oldmeta
.st_mode
)) == -1)
861 if (ftruncate(fd
, size
) == -1)
864 if (!preserve_acl(oldfd
, fd
) || !preserve_selinux_context(oldfd
, fd
))
866 /* change owner if necessary */
867 if (oldmeta
.st_uid
!= getuid() && fchown(fd
, oldmeta
.st_uid
, (uid_t
)-1) == -1)
869 /* change group if necessary, in case of failure some editors reset
870 * the group permissions to the same as for others */
871 if (oldmeta
.st_gid
!= getgid() && fchown(fd
, (uid_t
)-1, oldmeta
.st_gid
) == -1)
876 void *buf
= mmap(NULL
, size
, PROT_WRITE
, MAP_SHARED
, fd
, 0);
877 if (buf
== MAP_FAILED
)
883 for (Iterator it
= text_iterator_get(txt
, range
->start
);
884 rem
> 0 && text_iterator_valid(&it
);
885 text_iterator_next(&it
)) {
886 size_t len
= it
.end
- it
.text
;
889 memcpy(cur
, it
.text
, len
);
894 if (munmap(buf
, size
) == -1)
906 if (fstat(fd
, &meta
) == -1)
909 if (close(fd
) == -1) {
915 if (rename(tmpname
, filename
) == -1)
928 if (tmpname
&& *tmpname
)
935 bool text_save(Text
*txt
, const char *filename
) {
936 Filerange r
= (Filerange
){ .start
= 0, .end
= text_size(txt
) };
937 return text_save_range(txt
, &r
, filename
);
940 /* First try to save the file atomically using rename(2) if this does not
941 * work overwrite the file in place. However if something goes wrong during
942 * this overwrite the original file is permanently damaged.
944 bool text_save_range(Text
*txt
, Filerange
*range
, const char *filename
) {
946 int fd
= -1, newfd
= -1;
948 if (!filename
|| text_save_atomic_range(txt
, range
, filename
))
952 if ((fd
= open(filename
, O_CREAT
|O_WRONLY
, S_IRUSR
|S_IWUSR
)) == -1)
954 if (fstat(fd
, &meta
) == -1)
956 if (meta
.st_dev
== txt
->info
.st_dev
&& meta
.st_ino
== txt
->info
.st_ino
&&
957 txt
->buf
&& txt
->buf
->type
== MMAP
&& txt
->buf
->size
) {
958 /* The file we are going to overwrite is currently mmap-ed from
959 * text_load, therefore we copy the mmap-ed buffer to a temporary
960 * file and remap it at the same position such that all pointers
961 * from the various pieces are still valid.
963 size_t size
= txt
->buf
->size
;
964 char tmpname
[32] = "/tmp/vis-XXXXXX";
965 mode_t mask
= umask(S_IXUSR
| S_IRWXG
| S_IRWXO
);
966 newfd
= mkstemp(tmpname
);
970 if (unlink(tmpname
) == -1)
972 ssize_t written
= write_all(newfd
, txt
->buf
->data
, size
);
973 if (written
== -1 || (size_t)written
!= size
)
975 if (munmap(txt
->buf
->data
, size
) == -1)
978 void *data
= mmap(txt
->buf
->data
, size
, PROT_READ
, MAP_SHARED
, newfd
, 0);
979 if (data
== MAP_FAILED
)
981 if (data
!= txt
->buf
->data
) {
985 if (close(newfd
) == -1) {
989 txt
->buf
->data
= data
;
992 /* overwrite the exisiting file content, if somehting goes wrong
993 * here we are screwed, TODO: make a backup before? */
994 if (ftruncate(fd
, 0) == -1)
996 ssize_t written
= text_write_range(txt
, range
, fd
);
997 if (written
== -1 || (size_t)written
!= text_range_size(range
))
1000 if (fsync(fd
) == -1)
1002 if (fstat(fd
, &meta
) == -1)
1004 if (close(fd
) == -1)
1008 txt
->saved_action
= txt
->history
;
1019 ssize_t
text_write(Text
*txt
, int fd
) {
1020 Filerange r
= (Filerange
){ .start
= 0, .end
= text_size(txt
) };
1021 return text_write_range(txt
, &r
, fd
);
1024 ssize_t
text_write_range(Text
*txt
, Filerange
*range
, int fd
) {
1025 size_t size
= text_range_size(range
), rem
= size
;
1026 for (Iterator it
= text_iterator_get(txt
, range
->start
);
1027 rem
> 0 && text_iterator_valid(&it
);
1028 text_iterator_next(&it
)) {
1029 size_t prem
= it
.end
- it
.text
;
1032 ssize_t written
= write_all(fd
, it
.text
, prem
);
1036 if ((size_t)written
!= prem
)
1042 /* load the given file as starting point for further editing operations.
1043 * to start with an empty document, pass NULL as filename. */
1044 Text
*text_load(const char *filename
) {
1045 Text
*txt
= calloc(1, sizeof(Text
));
1049 piece_init(&txt
->begin
, NULL
, &txt
->end
, NULL
, 0);
1050 piece_init(&txt
->end
, &txt
->begin
, NULL
, NULL
, 0);
1051 lineno_cache_invalidate(&txt
->lines
);
1053 if ((fd
= open(filename
, O_RDONLY
)) == -1)
1055 if (fstat(fd
, &txt
->info
) == -1)
1057 if (!S_ISREG(txt
->info
.st_mode
)) {
1058 errno
= S_ISDIR(txt
->info
.st_mode
) ? EISDIR
: ENOTSUP
;
1061 // XXX: use lseek(fd, 0, SEEK_END); instead?
1062 size_t size
= txt
->info
.st_size
;
1063 if (size
< BUFFER_MMAP_SIZE
)
1064 txt
->buf
= buffer_read(txt
, size
, fd
);
1066 txt
->buf
= buffer_mmap(txt
, size
, fd
, 0);
1069 Piece
*p
= piece_alloc(txt
);
1072 piece_init(&txt
->begin
, NULL
, p
, NULL
, 0);
1073 piece_init(p
, &txt
->begin
, &txt
->end
, txt
->buf
->data
, txt
->buf
->len
);
1074 piece_init(&txt
->end
, p
, NULL
, NULL
, 0);
1075 txt
->size
= txt
->buf
->len
;
1077 /* write an empty action */
1078 change_alloc(txt
, EPOS
);
1080 txt
->saved_action
= txt
->history
;
1092 struct stat
text_stat(Text
*txt
) {
1096 /* A delete operation can either start/stop midway through a piece or at
1097 * a boundry. In the former case a new piece is created to represent the
1098 * remaining text before/after the modification point.
1100 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
1101 * | | | existing| |demo | |text | | |
1102 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
1104 * |------ delete range -----|
1106 * /-+ --> +----+ --> +--+ --> +-\
1107 * | | | exi| |t | | |
1108 * \-+ <-- +----+ <-- +--+ <-- +-/
1110 bool text_delete(Text
*txt
, size_t pos
, size_t len
) {
1113 if (pos
+ len
> txt
->size
)
1115 if (pos
< txt
->lines
.pos
)
1116 lineno_cache_invalidate(&txt
->lines
);
1118 Location loc
= piece_get_intern(txt
, pos
);
1119 Piece
*p
= loc
.piece
;
1122 size_t off
= loc
.off
;
1123 if (cache_delete(txt
, p
, off
, len
))
1125 Change
*c
= change_alloc(txt
, pos
);
1129 bool midway_start
= false, midway_end
= false; /* split pieces? */
1130 Piece
*before
, *after
; /* unmodified pieces before/after deletion point */
1131 Piece
*start
, *end
; /* span which is removed */
1132 size_t cur
; /* how much has already been deleted */
1134 if (off
== p
->len
) {
1135 /* deletion starts at a piece boundry */
1140 /* deletion starts midway through a piece */
1141 midway_start
= true;
1144 before
= piece_alloc(txt
);
1149 /* skip all pieces which fall into deletion range */
1156 /* deletion stops at a piece boundry */
1160 /* cur > len: deletion stops midway through a piece */
1163 after
= piece_alloc(txt
);
1166 piece_init(after
, before
, p
->next
, p
->data
+ p
->len
- (cur
- len
), cur
- len
);
1170 /* we finally know which piece follows our newly allocated before piece */
1171 piece_init(before
, start
->prev
, after
, start
->data
, off
);
1174 Piece
*new_start
= NULL
, *new_end
= NULL
;
1186 span_init(&c
->new, new_start
, new_end
);
1187 span_init(&c
->old
, start
, end
);
1188 span_swap(txt
, &c
->old
, &c
->new);
1192 bool text_delete_range(Text
*txt
, Filerange
*r
) {
1193 if (!text_range_valid(r
))
1195 return text_delete(txt
, r
->start
, text_range_size(r
));
1198 /* preserve the current text content such that it can be restored by
1199 * means of undo/redo operations */
1200 void text_snapshot(Text
*txt
) {
1201 if (txt
->current_action
)
1202 txt
->last_action
= txt
->current_action
;
1203 txt
->current_action
= NULL
;
1208 void text_free(Text
*txt
) {
1213 Action
*hist
= txt
->history
;
1214 while (hist
&& hist
->prev
)
1217 Action
*later
= hist
->later
;
1222 for (Piece
*next
, *p
= txt
->pieces
; p
; p
= next
) {
1223 next
= p
->global_next
;
1227 for (Buffer
*next
, *buf
= txt
->buffers
; buf
; buf
= next
) {
1235 bool text_modified(Text
*txt
) {
1236 return txt
->saved_action
!= txt
->history
;
1239 bool text_sigbus(Text
*txt
, const char *addr
) {
1240 for (Buffer
*buf
= txt
->buffers
; buf
; buf
= buf
->next
) {
1241 if (buf
->type
== MMAP
&& buf
->data
<= addr
&& addr
< buf
->data
+ buf
->size
)
1247 enum TextNewLine
text_newline_type(Text
*txt
){
1248 if (!txt
->newlines
) {
1249 txt
->newlines
= TEXT_NEWLINE_NL
; /* default to UNIX style \n new lines */
1250 const char *start
= txt
->buf
? txt
->buf
->data
: NULL
;
1252 const char *nl
= memchr(start
, '\n', txt
->buf
->len
);
1253 if (nl
> start
&& nl
[-1] == '\r')
1254 txt
->newlines
= TEXT_NEWLINE_CRNL
;
1257 size_t nl
= lines_skip_forward(txt
, 0, 1, NULL
);
1258 if (nl
> 1 && text_byte_get(txt
, nl
-2, &c
) && c
== '\r')
1259 txt
->newlines
= TEXT_NEWLINE_CRNL
;
1263 return txt
->newlines
;
1266 const char *text_newline_char(Text
*txt
) {
1267 static const char *types
[] = {
1268 [TEXT_NEWLINE_NL
] = "\n",
1269 [TEXT_NEWLINE_CRNL
] = "\r\n",
1271 return types
[text_newline_type(txt
)];
1274 static bool text_iterator_init(Iterator
*it
, size_t pos
, Piece
*p
, size_t off
) {
1278 .start
= p
? p
->data
: NULL
,
1279 .end
= p
? p
->data
+ p
->len
: NULL
,
1280 .text
= p
? p
->data
+ off
: NULL
,
1282 return text_iterator_valid(it
);
1285 Iterator
text_iterator_get(Text
*txt
, size_t pos
) {
1287 Location loc
= piece_get_extern(txt
, pos
);
1288 text_iterator_init(&it
, pos
, loc
.piece
, loc
.off
);
1292 bool text_iterator_byte_get(Iterator
*it
, char *b
) {
1293 if (text_iterator_valid(it
)) {
1294 if (it
->start
<= it
->text
&& it
->text
< it
->end
) {
1297 } else if (it
->pos
== it
->piece
->text
->size
) { /* EOF */
1305 bool text_iterator_next(Iterator
*it
) {
1306 return text_iterator_init(it
, it
->pos
, it
->piece
? it
->piece
->next
: NULL
, 0);
1309 bool text_iterator_prev(Iterator
*it
) {
1310 return text_iterator_init(it
, it
->pos
, it
->piece
? it
->piece
->prev
: NULL
, 0);
1313 bool text_iterator_valid(const Iterator
*it
) {
1314 /* filter out sentinel nodes */
1315 return it
->piece
&& it
->piece
->text
;
1318 bool text_iterator_byte_next(Iterator
*it
, char *b
) {
1319 if (!text_iterator_valid(it
))
1322 /* special case for advancement to EOF */
1323 if (it
->text
== it
->end
&& !it
->piece
->next
->text
) {
1329 while (it
->text
>= it
->end
) {
1330 if (!text_iterator_next(it
))
1332 it
->text
= it
->start
;
1340 bool text_iterator_byte_prev(Iterator
*it
, char *b
) {
1341 if (!text_iterator_valid(it
))
1343 while (it
->text
== it
->start
) {
1344 if (!text_iterator_prev(it
))
1355 bool text_iterator_codepoint_next(Iterator
*it
, char *c
) {
1356 while (text_iterator_byte_next(it
, NULL
)) {
1357 if (ISUTF8(*it
->text
)) {
1366 bool text_iterator_codepoint_prev(Iterator
*it
, char *c
) {
1367 while (text_iterator_byte_prev(it
, NULL
)) {
1368 if (ISUTF8(*it
->text
)) {
1377 bool text_iterator_char_next(Iterator
*it
, char *c
) {
1378 if (!text_iterator_codepoint_next(it
, c
))
1380 mbstate_t ps
= { 0 };
1382 char buf
[MB_CUR_MAX
];
1383 size_t len
= text_bytes_get(it
->piece
->text
, it
->pos
, sizeof buf
, buf
);
1385 size_t wclen
= mbrtowc(&wc
, buf
, len
, &ps
);
1386 if (wclen
== (size_t)-1 && errno
== EILSEQ
) {
1388 } else if (wclen
== (size_t)-2) {
1390 } else if (wclen
== 0) {
1393 int width
= wcwidth(wc
);
1396 if (!text_iterator_codepoint_next(it
, c
))
1403 bool text_iterator_char_prev(Iterator
*it
, char *c
) {
1404 if (!text_iterator_codepoint_prev(it
, c
))
1407 char buf
[MB_CUR_MAX
];
1408 size_t len
= text_bytes_get(it
->piece
->text
, it
->pos
, sizeof buf
, buf
);
1410 mbstate_t ps
= { 0 };
1411 size_t wclen
= mbrtowc(&wc
, buf
, len
, &ps
);
1412 if (wclen
== (size_t)-1 && errno
== EILSEQ
) {
1414 } else if (wclen
== (size_t)-2) {
1416 } else if (wclen
== 0) {
1419 int width
= wcwidth(wc
);
1422 if (!text_iterator_codepoint_prev(it
, c
))
1429 bool text_byte_get(Text
*txt
, size_t pos
, char *buf
) {
1430 return text_bytes_get(txt
, pos
, 1, buf
);
1433 size_t text_bytes_get(Text
*txt
, size_t pos
, size_t len
, char *buf
) {
1438 text_iterate(txt
, it
, pos
) {
1441 size_t piece_len
= it
.end
- it
.text
;
1442 if (piece_len
> rem
)
1444 memcpy(cur
, it
.text
, piece_len
);
1451 char *text_bytes_alloc0(Text
*txt
, size_t pos
, size_t len
) {
1452 if (len
== SIZE_MAX
)
1454 char *buf
= malloc(len
+1);
1457 len
= text_bytes_get(txt
, pos
, len
, buf
);
1462 size_t text_size(Text
*txt
) {
1466 /* count the number of new lines '\n' in range [pos, pos+len) */
1467 static size_t lines_count(Text
*txt
, size_t pos
, size_t len
) {
1469 text_iterate(txt
, it
, pos
) {
1470 const char *start
= it
.text
;
1471 while (len
> 0 && start
< it
.end
) {
1472 size_t n
= MIN(len
, (size_t)(it
.end
- start
));
1473 const char *end
= memchr(start
, '\n', n
);
1479 len
-= end
- start
+ 1;
1489 /* skip n lines forward and return position afterwards */
1490 static size_t lines_skip_forward(Text
*txt
, size_t pos
, size_t lines
, size_t *lines_skipped
) {
1491 size_t lines_old
= lines
;
1492 text_iterate(txt
, it
, pos
) {
1493 const char *start
= it
.text
;
1494 while (lines
> 0 && start
< it
.end
) {
1495 size_t n
= it
.end
- start
;
1496 const char *end
= memchr(start
, '\n', n
);
1501 pos
+= end
- start
+ 1;
1510 *lines_skipped
= lines_old
- lines
;
1514 static void lineno_cache_invalidate(LineCache
*cache
) {
1519 size_t text_pos_by_lineno(Text
*txt
, size_t lineno
) {
1520 size_t lines_skipped
;
1521 LineCache
*cache
= &txt
->lines
;
1524 if (lineno
> cache
->lineno
) {
1525 cache
->pos
= lines_skip_forward(txt
, cache
->pos
, lineno
- cache
->lineno
, &lines_skipped
);
1526 cache
->lineno
+= lines_skipped
;
1527 } else if (lineno
< cache
->lineno
) {
1529 // TODO does it make sense to scan memory backwards here?
1530 size_t diff
= cache
->lineno
- lineno
;
1531 if (diff
< lineno
) {
1532 lines_skip_backward(txt
, cache
->pos
, diff
);
1535 cache
->pos
= lines_skip_forward(txt
, 0, lineno
- 1, &lines_skipped
);
1536 cache
->lineno
= lines_skipped
+ 1;
1538 return cache
->lineno
== lineno
? cache
->pos
: EPOS
;
1541 size_t text_lineno_by_pos(Text
*txt
, size_t pos
) {
1542 LineCache
*cache
= &txt
->lines
;
1543 if (pos
> txt
->size
)
1545 if (pos
< cache
->pos
) {
1546 size_t diff
= cache
->pos
- pos
;
1548 cache
->lineno
-= lines_count(txt
, pos
, diff
);
1550 cache
->lineno
= lines_count(txt
, 0, pos
) + 1;
1551 } else if (pos
> cache
->pos
) {
1552 cache
->lineno
+= lines_count(txt
, cache
->pos
, pos
- cache
->pos
);
1555 return cache
->lineno
;
1558 Mark
text_mark_set(Text
*txt
, size_t pos
) {
1560 return (Mark
)&txt
->begin
;
1561 if (pos
== txt
->size
)
1562 return (Mark
)&txt
->end
;
1563 Location loc
= piece_get_extern(txt
, pos
);
1566 return loc
.piece
->data
+ loc
.off
;
1569 size_t text_mark_get(Text
*txt
, Mark mark
) {
1574 if (mark
== (Mark
)&txt
->begin
)
1576 if (mark
== (Mark
)&txt
->end
)
1579 for (Piece
*p
= txt
->begin
.next
; p
->next
; p
= p
->next
) {
1580 if (p
->data
<= mark
&& mark
< p
->data
+ p
->len
)
1581 return cur
+ (mark
- p
->data
);
1588 size_t text_history_get(Text
*txt
, size_t index
) {
1589 for (Action
*a
= txt
->current_action
? txt
->current_action
: txt
->history
; a
; a
= a
->prev
) {
1591 Change
*c
= a
->change
;
1592 while (c
&& c
->next
)
1594 return c
? c
->pos
: EPOS
;