Permalink
Browse files

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).
  • Loading branch information...
xolox committed Jun 29, 2014
1 parent fc75b64 commit 8dde3d1a3090979fd97565382a31bba33238c74c
Showing with 82 additions and 33 deletions.
  1. +14 −5 README.md
  2. +2 −2 autoload/xolox/misc.vim
  3. +46 −14 autoload/xolox/misc/perm.vim
  4. +2 −5 autoload/xolox/misc/persist.vim
  5. +18 −7 doc/misc.txt
View
@@ -37,8 +37,8 @@ from the source code of the miscellaneous scripts using the Python module
<!-- Start of generated documentation -->
-The documentation of the 91 functions below was extracted from
-19 Vim scripts on June 29, 2014 at 23:33.
+The documentation of the 92 functions below was extracted from
+19 Vim scripts on June 30, 2014 at 00:19.
### Asynchronous Vim script evaluation
@@ -658,15 +658,24 @@ my tags file because it's now owned by root … ಠ_ಠ
[vim-easytags]: http://peterodding.com/code/vim/easytags/
[writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
+#### The `xolox#misc#perm#update()` function
+
+Atomically update a file's contents while preserving the owner, group and
+mode. The first argument is the pathname of the file to update (a string).
+The second argument is the list of lines to be written to the file. Writes
+the new contents to a temporary file and renames the temporary file into
+place, thereby preventing readers from reading a partially written file.
+
#### The `xolox#misc#perm#get()` function
-Get the permissions of the pathname given as the first argument. Returns a
-string which you can later pass to `xolox#misc#perm#set()`.
+Get the owner, group and permissions of the pathname given as the first
+argument. Returns an opaque value which you can later pass to
+`xolox#misc#perm#set()`.
#### The `xolox#misc#perm#set()` function
Set the permissions (the second argument) of the pathname given as the
-first argument. Expects a permissions string created by
+first argument. Expects a permissions value created by
`xolox#misc#perm#get()`.
### Persist/recall Vim values from/to files
View
@@ -1,7 +1,7 @@
" The version of my miscellaneous scripts.
"
" Author: Peter Odding <peter@peterodding.com>
-" Last Change: June 29, 2014
+" Last Change: June 30, 2014
" URL: http://peterodding.com/code/vim/misc/
-let g:xolox#misc#version = '1.12'
+let g:xolox#misc#version = '1.13'
@@ -1,7 +1,7 @@
" Manipulation of UNIX file permissions.
"
" Author: Peter Odding <peter@peterodding.com>
-" Last Change: June 29, 2014
+" Last Change: June 30, 2014
" URL: http://peterodding.com/code/vim/misc/
"
" Vim's [writefile()][] function cannot set file permissions for newly created
@@ -26,36 +26,68 @@
" [vim-easytags]: http://peterodding.com/code/vim/easytags/
" [writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
+function! xolox#misc#perm#update(fname, contents)
+ " Atomically update a file's contents while preserving the owner, group and
+ " mode. The first argument is the pathname of the file to update (a string).
+ " The second argument is the list of lines to be written to the file. Writes
+ " the new contents to a temporary file and renames the temporary file into
+ " place, thereby preventing readers from reading a partially written file.
+ let starttime = xolox#misc#timer#start()
+ let temporary_file = printf('%s.tmp', a:fname)
+ 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)
+ if writefile(a:contents, temporary_file) == 0
+ call xolox#misc#perm#set(temporary_file, xolox#misc#perm#get(a:fname))
+ call xolox#misc#msg#debug("vim-misc %s: Replacing %s with %s ..", g:xolox#misc#version, a:fname, temporary_file)
+ if rename(temporary_file, a:fname) == 0
+ call xolox#misc#timer#stop("vim-misc %s: Successfully updated %s using atomic rename in %s.", g:xolox#misc#version, a:fname, starttime)
+ return 1
+ endif
+ endif
+ if filereadable(temporary_file)
+ call delete(temporary_file)
+ endif
+ return 0
+endfunction
+
function! xolox#misc#perm#get(fname)
- " Get the permissions of the pathname given as the first argument. Returns a
- " string which you can later pass to `xolox#misc#perm#set()`.
+ " Get the owner, group and permissions of the pathname given as the first
+ " argument. Returns an opaque value which you can later pass to
+ " `xolox#misc#perm#set()`.
let pathname = xolox#misc#path#absolute(a:fname)
if filereadable(pathname)
- let command = printf('stat --format %%a %s', shellescape(pathname))
+ let command = printf('stat --format %s %s', '%U:%G:%a', shellescape(pathname))
let result = xolox#misc#os#exec({'command': command})
if result['exit_code'] == 0 && len(result['stdout']) >= 1
- let permissions_string = '0' . xolox#misc#str#trim(result['stdout'][0])
- if permissions_string =~ '^[0-7]\+$'
- call xolox#misc#msg#debug("vim-misc %s: Found permissions of %s: %s.", g:xolox#misc#version, pathname, permissions_string)
- return permissions_string
+ let tokens = split(result['stdout'][0], ':')
+ if len(tokens) == 3
+ let [owner, group, mode] = tokens
+ let mode = '0' . mode
+ 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)
+ return [owner, group, mode]
endif
endif
endif
- return ''
+ return []
endfunction
function! xolox#misc#perm#set(fname, perms)
" Set the permissions (the second argument) of the pathname given as the
- " first argument. Expects a permissions string created by
+ " first argument. Expects a permissions value created by
" `xolox#misc#perm#get()`.
if !empty(a:perms)
let pathname = xolox#misc#path#absolute(a:fname)
- let command = printf('chmod %s %s', shellescape(a:perms), shellescape(pathname))
- let result = xolox#misc#os#exec({'command': command})
- if result['exit_code'] == 0
- call xolox#misc#msg#debug("vim-misc %s: Successfully set permissions of %s to %s.", g:xolox#misc#version, pathname, a:perms)
+ let [owner, group, mode] = a:perms
+ if s:run('chown %s:%s %s', owner, group, pathname) && s:run('chmod %s %s', mode, pathname)
+ 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)
return 1
endif
endif
return 0
endfunction
+
+function! s:run(command, ...)
+ let args = map(copy(a:000), 'shellescape(v:val)')
+ call insert(args, a:command, 0)
+ let result = xolox#misc#os#exec({'command': call('printf', args)})
+ return result['exit_code'] == 0
+endfunction
@@ -1,7 +1,7 @@
" Persist/recall Vim values from/to files.
"
" Author: Peter Odding <peter@peterodding.com>
-" Last Change: June 22, 2014
+" Last Change: June 30, 2014
" URL: http://peterodding.com/code/vim/misc/
"
" 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
"
" [string()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#string()
" [writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
- let intermediate_file = printf('%s.tmp', a:filename)
- let lines = split(string(a:value), "\n")
- call writefile(lines, intermediate_file)
- call rename(intermediate_file, a:filename)
+ return xolox#misc#perm#update(a:filename, split(string(a:value), "\n"))
endfunction
" vim: ts=2 sw=2 et
View
@@ -65,8 +65,9 @@ Contents ~
11. The |xolox#misc#path#is_relative()| function
12. The |xolox#misc#path#tempdir()| function
13. Manipulation of UNIX file permissions |misc-manipulation-of-unix-file-permissions|
- 1. The |xolox#misc#perm#get()| function
- 2. The |xolox#misc#perm#set()| function
+ 1. The |xolox#misc#perm#update()| function
+ 2. The |xolox#misc#perm#get()| function
+ 3. The |xolox#misc#perm#set()| function
14. Persist/recall Vim values from/to files |misc-persist-recall-vim-values-from-to-files|
1. The |xolox#misc#persist#load()| function
2. The |xolox#misc#persist#save()| function
@@ -165,8 +166,8 @@ For those who are curious: The function descriptions given below were extracted
from the source code of the miscellaneous scripts using the Python module
'vimdoctool.py' included in vim-tools [5].
-The documentation of the 91 functions below was extracted from 19 Vim scripts
-on June 29, 2014 at 23:33.
+The documentation of the 92 functions below was extracted from 19 Vim scripts
+on June 30, 2014 at 00:19.
-------------------------------------------------------------------------------
*misc-asynchronous-vim-script-evaluation*
@@ -796,17 +797,27 @@ file is written the file becomes owned by root (my effective user id in the
'sudo' session). Once I leave the 'sudo' session I can no longer update my tags
file because it's now owned by root … ಠ_ಠ
+-------------------------------------------------------------------------------
+The *xolox#misc#perm#update()* function
+
+Atomically update a file's contents while preserving the owner, group and mode.
+The first argument is the pathname of the file to update (a string). The second
+argument is the list of lines to be written to the file. Writes the new
+contents to a temporary file and renames the temporary file into place, thereby
+preventing readers from reading a partially written file.
+
-------------------------------------------------------------------------------
The *xolox#misc#perm#get()* function
-Get the permissions of the pathname given as the first argument. Returns a
-string which you can later pass to |xolox#misc#perm#set()|.
+Get the owner, group and permissions of the pathname given as the first
+argument. Returns an opaque value which you can later pass to
+|xolox#misc#perm#set()|.
-------------------------------------------------------------------------------
The *xolox#misc#perm#set()* function
Set the permissions (the second argument) of the pathname given as the first
-argument. Expects a permissions string created by |xolox#misc#perm#get()|.
+argument. Expects a permissions value created by |xolox#misc#perm#get()|.
-------------------------------------------------------------------------------
*misc-persist-recall-vim-values-from-to-files*

0 comments on commit 8dde3d1

Please sign in to comment.