14 #include <selinux/selinux.h>
18 #include "text-internal.h"
19 #include "text-util.h"
22 struct TextSave
{ /* used to hold context between text_save_{begin,commit} calls */
23 Text
*txt
; /* text to operate on */
24 char *filename
; /* filename to save to as given to text_save_begin */
25 char *tmpname
; /* temporary name used for atomic rename(2) */
26 int fd
; /* file descriptor to write data to using text_save_write */
27 int dirfd
; /* directory file descriptor, relative to which we save */
28 enum TextSaveMethod type
; /* method used to save file */
31 /* Allocate blocks holding the actual file content in chunks of size: */
33 #define BLOCK_SIZE (1 << 20)
35 /* Files smaller than this value are copied on load, larger ones are mmap(2)-ed
36 * directely. Hence the former can be truncated, while doing so on the latter
37 * results in havoc. */
38 #define BLOCK_MMAP_SIZE (1 << 26)
40 /* allocate a new block of MAX(size, BLOCK_SIZE) bytes */
41 Block
*block_alloc(size_t size
) {
42 Block
*blk
= calloc(1, sizeof *blk
);
45 if (BLOCK_SIZE
> size
)
47 if (!(blk
->data
= malloc(size
))) {
51 blk
->type
= BLOCK_TYPE_MALLOC
;
56 Block
*block_read(size_t size
, int fd
) {
57 Block
*blk
= block_alloc(size
);
60 char *data
= blk
->data
;
63 ssize_t len
= read(fd
, data
, rem
);
67 } else if (len
== 0) {
74 blk
->len
= size
- rem
;
78 Block
*block_mmap(size_t size
, int fd
, off_t offset
) {
79 Block
*blk
= calloc(1, sizeof *blk
);
83 blk
->data
= mmap(NULL
, size
, PROT_READ
, MAP_SHARED
, fd
, offset
);
84 if (blk
->data
== MAP_FAILED
) {
89 blk
->type
= BLOCK_TYPE_MMAP_ORIG
;
95 Block
*block_load(int dirfd
, const char *filename
, enum TextLoadMethod method
, struct stat
*info
) {
97 int fd
= openat(dirfd
, filename
, O_RDONLY
);
100 if (fstat(fd
, info
) == -1)
102 if (!S_ISREG(info
->st_mode
)) {
103 errno
= S_ISDIR(info
->st_mode
) ? EISDIR
: ENOTSUP
;
107 // XXX: use lseek(fd, 0, SEEK_END); instead?
108 size_t size
= info
->st_size
;
111 if (method
== TEXT_LOAD_READ
|| (method
== TEXT_LOAD_AUTO
&& size
< BLOCK_MMAP_SIZE
))
112 block
= block_read(size
, fd
);
114 block
= block_mmap(size
, fd
, 0);
121 void block_free(Block
*blk
) {
124 if (blk
->type
== BLOCK_TYPE_MALLOC
)
126 else if ((blk
->type
== BLOCK_TYPE_MMAP_ORIG
|| blk
->type
== BLOCK_TYPE_MMAP
) && blk
->data
)
127 munmap(blk
->data
, blk
->size
);
131 /* check whether block has enough free space to store len bytes */
132 bool block_capacity(Block
*blk
, size_t len
) {
133 return blk
->size
- blk
->len
>= len
;
136 /* append data to block, assumes there is enough space available */
137 const char *block_append(Block
*blk
, const char *data
, size_t len
) {
138 char *dest
= memcpy(blk
->data
+ blk
->len
, data
, len
);
143 /* insert data into block at an arbitrary position, this should only be used with
144 * data of the most recently created piece. */
145 bool block_insert(Block
*blk
, size_t pos
, const char *data
, size_t len
) {
146 if (pos
> blk
->len
|| !block_capacity(blk
, len
))
149 return block_append(blk
, data
, len
);
150 char *insert
= blk
->data
+ pos
;
151 memmove(insert
+ len
, insert
, blk
->len
- pos
);
152 memcpy(insert
, data
, len
);
157 /* delete data from a block at an arbitrary position, this should only be used with
158 * data of the most recently created piece. */
159 bool block_delete(Block
*blk
, size_t pos
, size_t len
) {
161 if (!addu(pos
, len
, &end
) || end
> blk
->len
)
163 if (blk
->len
== pos
) {
167 char *delete = blk
->data
+ pos
;
168 memmove(delete, delete + len
, blk
->len
- pos
- len
);
173 Text
*text_load(const char *filename
) {
174 return text_load_method(filename
, TEXT_LOAD_AUTO
);
177 Text
*text_loadat(int dirfd
, const char *filename
) {
178 return text_loadat_method(dirfd
, filename
, TEXT_LOAD_AUTO
);
181 Text
*text_load_method(const char *filename
, enum TextLoadMethod method
) {
182 return text_loadat_method(AT_FDCWD
, filename
, method
);
185 static ssize_t
write_all(int fd
, const char *buf
, size_t count
) {
188 ssize_t written
= write(fd
, buf
, rem
> INT_MAX
? INT_MAX
: rem
);
190 if (errno
== EAGAIN
|| errno
== EINTR
)
193 } else if (written
== 0) {
202 static bool preserve_acl(int src
, int dest
) {
204 acl_t acl
= acl_get_fd(src
);
206 return errno
== ENOTSUP
? true : false;
207 if (acl_set_fd(dest
, acl
) == -1) {
212 #endif /* CONFIG_ACL */
216 static bool preserve_selinux_context(int src
, int dest
) {
218 char *context
= NULL
;
219 if (!is_selinux_enabled())
221 if (fgetfilecon(src
, &context
) == -1)
222 return errno
== ENOTSUP
? true : false;
223 if (fsetfilecon(dest
, context
) == -1) {
228 #endif /* CONFIG_SELINUX */
232 static int mkstempat(int dirfd
, char *template) {
233 if (dirfd
== AT_FDCWD
)
234 return mkstemp(template);
235 // FIXME: not thread safe
237 int cwd
= open(".", O_RDONLY
|O_DIRECTORY
);
240 if (fchdir(dirfd
) == -1)
242 fd
= mkstemp(template);
251 /* Create a new file named `.filename.vis.XXXXXX` (where `XXXXXX` is a
252 * randomly generated, unique suffix) and try to preserve all important
253 * meta data. After the file content has been written to this temporary
254 * file, text_save_commit_atomic will atomically move it to its final
255 * (possibly already existing) destination using rename(2).
257 * This approach does not work if:
259 * - the file is a symbolic link
260 * - the file is a hard link
261 * - file ownership can not be preserved
262 * - file group can not be preserved
263 * - directory permissions do not allow creation of a new file
264 * - POSXI ACL can not be preserved (if enabled)
265 * - SELinux security context can not be preserved (if enabled)
267 static bool text_save_begin_atomic(TextSave
*ctx
) {
268 int oldfd
, saved_errno
;
269 if ((oldfd
= openat(ctx
->dirfd
, ctx
->filename
, O_RDONLY
)) == -1 && errno
!= ENOENT
)
271 struct stat oldmeta
= { 0 };
272 if (oldfd
!= -1 && fstatat(ctx
->dirfd
, ctx
->filename
, &oldmeta
, AT_SYMLINK_NOFOLLOW
) == -1)
275 if (S_ISLNK(oldmeta
.st_mode
)) /* symbolic link */
277 if (oldmeta
.st_nlink
> 1) /* hard link */
281 char suffix
[] = ".vis.XXXXXX";
282 size_t len
= strlen(ctx
->filename
) + sizeof("./.") + sizeof(suffix
);
283 char *dir
= strdup(ctx
->filename
);
284 char *base
= strdup(ctx
->filename
);
286 if (!(ctx
->tmpname
= malloc(len
)) || !dir
|| !base
) {
292 snprintf(ctx
->tmpname
, len
, "%s/.%s%s", dirname(dir
), basename(base
), suffix
);
296 if ((ctx
->fd
= mkstempat(ctx
->dirfd
, ctx
->tmpname
)) == -1)
300 mode_t mask
= umask(0);
302 if (fchmod(ctx
->fd
, 0666 & ~mask
) == -1)
305 if (fchmod(ctx
->fd
, oldmeta
.st_mode
) == -1)
307 if (!preserve_acl(oldfd
, ctx
->fd
) || !preserve_selinux_context(oldfd
, ctx
->fd
))
309 /* change owner if necessary */
310 if (oldmeta
.st_uid
!= getuid() && fchown(ctx
->fd
, oldmeta
.st_uid
, (uid_t
)-1) == -1)
312 /* change group if necessary, in case of failure some editors reset
313 * the group permissions to the same as for others */
314 if (oldmeta
.st_gid
!= getgid() && fchown(ctx
->fd
, (uid_t
)-1, oldmeta
.st_gid
) == -1)
319 ctx
->type
= TEXT_SAVE_ATOMIC
;
334 static bool text_save_commit_atomic(TextSave
*ctx
) {
335 if (fsync(ctx
->fd
) == -1)
338 struct stat meta
= { 0 };
339 if (fstat(ctx
->fd
, &meta
) == -1)
342 bool close_failed
= (close(ctx
->fd
) == -1);
347 if (renameat(ctx
->dirfd
, ctx
->tmpname
, ctx
->dirfd
, ctx
->filename
) == -1)
353 int dir
= openat(ctx
->dirfd
, dirname(ctx
->filename
), O_DIRECTORY
|O_RDONLY
);
357 if (fsync(dir
) == -1 && errno
!= EINVAL
) {
362 if (close(dir
) == -1)
365 text_saved(ctx
->txt
, &meta
);
369 static bool text_save_begin_inplace(TextSave
*ctx
) {
370 Text
*txt
= ctx
->txt
;
371 struct stat now
= { 0 };
372 int newfd
= -1, saved_errno
;
373 if ((ctx
->fd
= openat(ctx
->dirfd
, ctx
->filename
, O_CREAT
|O_WRONLY
, 0666)) == -1)
375 if (fstat(ctx
->fd
, &now
) == -1)
377 struct stat loaded
= text_stat(txt
);
378 Block
*block
= text_block_mmaped(txt
);
379 if (block
&& now
.st_dev
== loaded
.st_dev
&& now
.st_ino
== loaded
.st_ino
) {
380 /* The file we are going to overwrite is currently mmap-ed from
381 * text_load, therefore we copy the mmap-ed block to a temporary
382 * file and remap it at the same position such that all pointers
383 * from the various pieces are still valid.
385 size_t size
= block
->size
;
386 char tmpname
[32] = "/tmp/vis-XXXXXX";
387 newfd
= mkstemp(tmpname
);
390 if (unlink(tmpname
) == -1)
392 ssize_t written
= write_all(newfd
, block
->data
, size
);
393 if (written
== -1 || (size_t)written
!= size
)
395 void *data
= mmap(block
->data
, size
, PROT_READ
, MAP_SHARED
|MAP_FIXED
, newfd
, 0);
396 if (data
== MAP_FAILED
)
398 bool close_failed
= (close(newfd
) == -1);
402 block
->type
= BLOCK_TYPE_MMAP
;
404 /* overwrite the existing file content, if something goes wrong
405 * here we are screwed, TODO: make a backup before? */
406 if (ftruncate(ctx
->fd
, 0) == -1)
408 ctx
->type
= TEXT_SAVE_INPLACE
;
421 static bool text_save_commit_inplace(TextSave
*ctx
) {
422 if (fsync(ctx
->fd
) == -1)
424 struct stat meta
= { 0 };
425 if (fstat(ctx
->fd
, &meta
) == -1)
427 if (close(ctx
->fd
) == -1)
429 text_saved(ctx
->txt
, &meta
);
433 TextSave
*text_save_begin(Text
*txt
, int dirfd
, const char *filename
, enum TextSaveMethod type
) {
436 TextSave
*ctx
= calloc(1, sizeof *ctx
);
442 if (!(ctx
->filename
= strdup(filename
)))
445 if ((type
== TEXT_SAVE_AUTO
|| type
== TEXT_SAVE_ATOMIC
) && text_save_begin_atomic(ctx
))
449 if ((type
== TEXT_SAVE_AUTO
|| type
== TEXT_SAVE_INPLACE
) && text_save_begin_inplace(ctx
))
452 text_save_cancel(ctx
);
456 bool text_save_commit(TextSave
*ctx
) {
461 case TEXT_SAVE_ATOMIC
:
462 ret
= text_save_commit_atomic(ctx
);
464 case TEXT_SAVE_INPLACE
:
465 ret
= text_save_commit_inplace(ctx
);
472 text_save_cancel(ctx
);
476 void text_save_cancel(TextSave
*ctx
) {
479 int saved_errno
= errno
;
482 if (ctx
->tmpname
&& ctx
->tmpname
[0])
483 unlinkat(ctx
->dirfd
, ctx
->tmpname
, 0);
490 /* First try to save the file atomically using rename(2) if this does not
491 * work overwrite the file in place. However if something goes wrong during
492 * this overwrite the original file is permanently damaged.
494 bool text_save(Text
*txt
, const char *filename
) {
495 return text_saveat(txt
, AT_FDCWD
, filename
);
498 bool text_saveat(Text
*txt
, int dirfd
, const char *filename
) {
499 return text_saveat_method(txt
, dirfd
, filename
, TEXT_SAVE_AUTO
);
502 bool text_save_method(Text
*txt
, const char *filename
, enum TextSaveMethod method
) {
503 return text_saveat_method(txt
, AT_FDCWD
, filename
, method
);
506 bool text_saveat_method(Text
*txt
, int dirfd
, const char *filename
, enum TextSaveMethod method
) {
508 text_saved(txt
, NULL
);
511 TextSave
*ctx
= text_save_begin(txt
, dirfd
, filename
, method
);
514 Filerange range
= (Filerange
){ .start
= 0, .end
= text_size(txt
) };
515 ssize_t written
= text_save_write_range(ctx
, &range
);
516 if (written
== -1 || (size_t)written
!= text_range_size(&range
)) {
517 text_save_cancel(ctx
);
520 return text_save_commit(ctx
);
523 ssize_t
text_save_write_range(TextSave
*ctx
, const Filerange
*range
) {
524 return text_write_range(ctx
->txt
, range
, ctx
->fd
);
527 ssize_t
text_write(const Text
*txt
, int fd
) {
528 Filerange r
= (Filerange
){ .start
= 0, .end
= text_size(txt
) };
529 return text_write_range(txt
, &r
, fd
);
532 ssize_t
text_write_range(const Text
*txt
, const Filerange
*range
, int fd
) {
533 size_t size
= text_range_size(range
), rem
= size
;
534 for (Iterator it
= text_iterator_get(txt
, range
->start
);
535 rem
> 0 && text_iterator_valid(&it
);
536 text_iterator_next(&it
)) {
537 size_t prem
= it
.end
- it
.text
;
540 ssize_t written
= write_all(fd
, it
.text
, prem
);
544 if ((size_t)written
!= prem
)