Skip to content

Commit 8dde3d1

Browse files
committed
Support for 'atomic' file updates (preserving owner, group and mode)
To be used in the feature branch xolox/vim-easytags#async-take-two of the vim-easytags plug-in (see also xolox/vim-easytags#84) in order to preserve the permissions of tags files (as explained in the docs).
1 parent fc75b64 commit 8dde3d1

File tree

5 files changed

+82
-33
lines changed

5 files changed

+82
-33
lines changed

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ from the source code of the miscellaneous scripts using the Python module
3737

3838
<!-- Start of generated documentation -->
3939

40-
The documentation of the 91 functions below was extracted from
41-
19 Vim scripts on June 29, 2014 at 23:33.
40+
The documentation of the 92 functions below was extracted from
41+
19 Vim scripts on June 30, 2014 at 00:19.
4242

4343
### Asynchronous Vim script evaluation
4444

@@ -658,15 +658,24 @@ my tags file because it's now owned by root … ಠ_ಠ
658658
[vim-easytags]: http://peterodding.com/code/vim/easytags/
659659
[writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
660660

661+
#### The `xolox#misc#perm#update()` function
662+
663+
Atomically update a file's contents while preserving the owner, group and
664+
mode. The first argument is the pathname of the file to update (a string).
665+
The second argument is the list of lines to be written to the file. Writes
666+
the new contents to a temporary file and renames the temporary file into
667+
place, thereby preventing readers from reading a partially written file.
668+
661669
#### The `xolox#misc#perm#get()` function
662670

663-
Get the permissions of the pathname given as the first argument. Returns a
664-
string which you can later pass to `xolox#misc#perm#set()`.
671+
Get the owner, group and permissions of the pathname given as the first
672+
argument. Returns an opaque value which you can later pass to
673+
`xolox#misc#perm#set()`.
665674

666675
#### The `xolox#misc#perm#set()` function
667676

668677
Set the permissions (the second argument) of the pathname given as the
669-
first argument. Expects a permissions string created by
678+
first argument. Expects a permissions value created by
670679
`xolox#misc#perm#get()`.
671680

672681
### Persist/recall Vim values from/to files

autoload/xolox/misc.vim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
" The version of my miscellaneous scripts.
22
"
33
" Author: Peter Odding <[email protected]>
4-
" Last Change: June 29, 2014
4+
" Last Change: June 30, 2014
55
" URL: http://peterodding.com/code/vim/misc/
66

7-
let g:xolox#misc#version = '1.12'
7+
let g:xolox#misc#version = '1.13'

autoload/xolox/misc/perm.vim

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
" Manipulation of UNIX file permissions.
22
"
33
" Author: Peter Odding <[email protected]>
4-
" Last Change: June 29, 2014
4+
" Last Change: June 30, 2014
55
" URL: http://peterodding.com/code/vim/misc/
66
"
77
" Vim's [writefile()][] function cannot set file permissions for newly created
@@ -26,36 +26,68 @@
2626
" [vim-easytags]: http://peterodding.com/code/vim/easytags/
2727
" [writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
2828

29+
function! xolox#misc#perm#update(fname, contents)
30+
" Atomically update a file's contents while preserving the owner, group and
31+
" mode. The first argument is the pathname of the file to update (a string).
32+
" The second argument is the list of lines to be written to the file. Writes
33+
" the new contents to a temporary file and renames the temporary file into
34+
" place, thereby preventing readers from reading a partially written file.
35+
let starttime = xolox#misc#timer#start()
36+
let temporary_file = printf('%s.tmp', a:fname)
37+
call xolox#misc#msg#debug("vim-misc %s: Writing new contents of %s to temporary file %s ..", g:xolox#misc#version, a:fname, temporary_file)
38+
if writefile(a:contents, temporary_file) == 0
39+
call xolox#misc#perm#set(temporary_file, xolox#misc#perm#get(a:fname))
40+
call xolox#misc#msg#debug("vim-misc %s: Replacing %s with %s ..", g:xolox#misc#version, a:fname, temporary_file)
41+
if rename(temporary_file, a:fname) == 0
42+
call xolox#misc#timer#stop("vim-misc %s: Successfully updated %s using atomic rename in %s.", g:xolox#misc#version, a:fname, starttime)
43+
return 1
44+
endif
45+
endif
46+
if filereadable(temporary_file)
47+
call delete(temporary_file)
48+
endif
49+
return 0
50+
endfunction
51+
2952
function! xolox#misc#perm#get(fname)
30-
" Get the permissions of the pathname given as the first argument. Returns a
31-
" string which you can later pass to `xolox#misc#perm#set()`.
53+
" Get the owner, group and permissions of the pathname given as the first
54+
" argument. Returns an opaque value which you can later pass to
55+
" `xolox#misc#perm#set()`.
3256
let pathname = xolox#misc#path#absolute(a:fname)
3357
if filereadable(pathname)
34-
let command = printf('stat --format %%a %s', shellescape(pathname))
58+
let command = printf('stat --format %s %s', '%U:%G:%a', shellescape(pathname))
3559
let result = xolox#misc#os#exec({'command': command})
3660
if result['exit_code'] == 0 && len(result['stdout']) >= 1
37-
let permissions_string = '0' . xolox#misc#str#trim(result['stdout'][0])
38-
if permissions_string =~ '^[0-7]\+$'
39-
call xolox#misc#msg#debug("vim-misc %s: Found permissions of %s: %s.", g:xolox#misc#version, pathname, permissions_string)
40-
return permissions_string
61+
let tokens = split(result['stdout'][0], ':')
62+
if len(tokens) == 3
63+
let [owner, group, mode] = tokens
64+
let mode = '0' . mode
65+
call xolox#misc#msg#debug("vim-misc %s: File %s has owner %s, group %s, mode %s.", g:xolox#misc#version, pathname, owner, group, mode)
66+
return [owner, group, mode]
4167
endif
4268
endif
4369
endif
44-
return ''
70+
return []
4571
endfunction
4672

4773
function! xolox#misc#perm#set(fname, perms)
4874
" Set the permissions (the second argument) of the pathname given as the
49-
" first argument. Expects a permissions string created by
75+
" first argument. Expects a permissions value created by
5076
" `xolox#misc#perm#get()`.
5177
if !empty(a:perms)
5278
let pathname = xolox#misc#path#absolute(a:fname)
53-
let command = printf('chmod %s %s', shellescape(a:perms), shellescape(pathname))
54-
let result = xolox#misc#os#exec({'command': command})
55-
if result['exit_code'] == 0
56-
call xolox#misc#msg#debug("vim-misc %s: Successfully set permissions of %s to %s.", g:xolox#misc#version, pathname, a:perms)
79+
let [owner, group, mode] = a:perms
80+
if s:run('chown %s:%s %s', owner, group, pathname) && s:run('chmod %s %s', mode, pathname)
81+
call xolox#misc#msg#debug("vim-misc %s: Successfully set %s owner to %s, group to %s and permissions to %s.", g:xolox#misc#version, pathname, owner, group, mode)
5782
return 1
5883
endif
5984
endif
6085
return 0
6186
endfunction
87+
88+
function! s:run(command, ...)
89+
let args = map(copy(a:000), 'shellescape(v:val)')
90+
call insert(args, a:command, 0)
91+
let result = xolox#misc#os#exec({'command': call('printf', args)})
92+
return result['exit_code'] == 0
93+
endfunction

autoload/xolox/misc/persist.vim

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
" Persist/recall Vim values from/to files.
22
"
33
" Author: Peter Odding <[email protected]>
4-
" Last Change: June 22, 2014
4+
" Last Change: June 30, 2014
55
" URL: http://peterodding.com/code/vim/misc/
66
"
77
" Vim's [string()][] function can be used to serialize Vim script values like
@@ -44,10 +44,7 @@ function! xolox#misc#persist#save(filename, value) " {{{1
4444
"
4545
" [string()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#string()
4646
" [writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
47-
let intermediate_file = printf('%s.tmp', a:filename)
48-
let lines = split(string(a:value), "\n")
49-
call writefile(lines, intermediate_file)
50-
call rename(intermediate_file, a:filename)
47+
return xolox#misc#perm#update(a:filename, split(string(a:value), "\n"))
5148
endfunction
5249

5350
" vim: ts=2 sw=2 et

doc/misc.txt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ Contents ~
6565
11. The |xolox#misc#path#is_relative()| function
6666
12. The |xolox#misc#path#tempdir()| function
6767
13. Manipulation of UNIX file permissions |misc-manipulation-of-unix-file-permissions|
68-
1. The |xolox#misc#perm#get()| function
69-
2. The |xolox#misc#perm#set()| function
68+
1. The |xolox#misc#perm#update()| function
69+
2. The |xolox#misc#perm#get()| function
70+
3. The |xolox#misc#perm#set()| function
7071
14. Persist/recall Vim values from/to files |misc-persist-recall-vim-values-from-to-files|
7172
1. The |xolox#misc#persist#load()| function
7273
2. The |xolox#misc#persist#save()| function
@@ -165,8 +166,8 @@ For those who are curious: The function descriptions given below were extracted
165166
from the source code of the miscellaneous scripts using the Python module
166167
'vimdoctool.py' included in vim-tools [5].
167168

168-
The documentation of the 91 functions below was extracted from 19 Vim scripts
169-
on June 29, 2014 at 23:33.
169+
The documentation of the 92 functions below was extracted from 19 Vim scripts
170+
on June 30, 2014 at 00:19.
170171

171172
-------------------------------------------------------------------------------
172173
*misc-asynchronous-vim-script-evaluation*
@@ -796,17 +797,27 @@ file is written the file becomes owned by root (my effective user id in the
796797
'sudo' session). Once I leave the 'sudo' session I can no longer update my tags
797798
file because it's now owned by root … ಠ_ಠ
798799

800+
-------------------------------------------------------------------------------
801+
The *xolox#misc#perm#update()* function
802+
803+
Atomically update a file's contents while preserving the owner, group and mode.
804+
The first argument is the pathname of the file to update (a string). The second
805+
argument is the list of lines to be written to the file. Writes the new
806+
contents to a temporary file and renames the temporary file into place, thereby
807+
preventing readers from reading a partially written file.
808+
799809
-------------------------------------------------------------------------------
800810
The *xolox#misc#perm#get()* function
801811

802-
Get the permissions of the pathname given as the first argument. Returns a
803-
string which you can later pass to |xolox#misc#perm#set()|.
812+
Get the owner, group and permissions of the pathname given as the first
813+
argument. Returns an opaque value which you can later pass to
814+
|xolox#misc#perm#set()|.
804815

805816
-------------------------------------------------------------------------------
806817
The *xolox#misc#perm#set()* function
807818

808819
Set the permissions (the second argument) of the pathname given as the first
809-
argument. Expects a permissions string created by |xolox#misc#perm#get()|.
820+
argument. Expects a permissions value created by |xolox#misc#perm#get()|.
810821

811822
-------------------------------------------------------------------------------
812823
*misc-persist-recall-vim-values-from-to-files*

0 commit comments

Comments
 (0)