pages tagged acl
yakking
http://yakking.branchable.com/tags/acl/
yakking
ikiwiki
2016-10-05T11:00:16Z
How difficult is it to preserve extended attributes when moving a file?
http://yakking.branchable.com/posts/moving-files-6-extended-attributes/
Richard Maw
2016-10-05T11:00:16Z
2016-10-05T11:00:09Z
<h1>Copying <a href="http://man7.org/linux/man-pages/man5/attr.5.html">extended attributes</a></h1>
<p><a href="http://man7.org/linux/man-pages/man5/attr.5.html">Extended attributes</a> allow you to provide extra metadata for a file.</p>
<p>It's effectively a key-value store using a string for the key,
but values can be arbitrary blobs.</p>
<p><a href="http://man7.org/linux/man-pages/man2/listxattr.2.html">flistxattr(2)</a> to know which xattrs exist.
<a href="http://man7.org/linux/man-pages/man2/getxattr.2.html">fgetxattr(2)</a> to read any xattrs.
<a href="http://man7.org/linux/man-pages/man2/setxattr.2.html">fsetxattr(2)</a> to write an xattr to a file.</p>
<p>Regular users can set <a href="http://man7.org/linux/man-pages/man5/attr.5.html">xattrs</a> beginning <code>user.</code>,
and as far as Linux is concerned that's arbitrary data
that it doesn't need to care what they are.</p>
<p>However there are attributes outside the <code>user.</code> namespace
which have special meaning to Linux,
so we shouldn't try to copy everything as it is.</p>
<div class="highlight-c"><pre class="hl"><span class="hl kwb">static int</span> <span class="hl kwd">realloc_double</span><span class="hl opt">(</span><span class="hl kwb">void</span> <span class="hl opt">**</span>buf<span class="hl opt">,</span> <span class="hl kwb">size_t</span> <span class="hl opt">*</span>size<span class="hl opt">) {</span>
<span class="hl kwb">size_t</span> new_size <span class="hl opt">= *</span>size <span class="hl opt">*</span> <span class="hl num">2</span><span class="hl opt">;</span>
<span class="hl kwb">void</span> <span class="hl opt">*</span>new_buf <span class="hl opt">=</span> <span class="hl kwd">realloc</span><span class="hl opt">(*</span>buf<span class="hl opt">,</span> new_size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>new_buf <span class="hl opt">==</span> NULL <span class="hl opt">&&</span> new_size <span class="hl opt">!=</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">return</span> <span class="hl opt">-</span><span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl opt">*</span>buf <span class="hl opt">=</span> new_buf<span class="hl opt">;</span>
<span class="hl opt">*</span>size <span class="hl opt">=</span> new_size<span class="hl opt">;</span>
<span class="hl kwa">return</span> <span class="hl num">0</span><span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwb">static int</span> <span class="hl kwd">xattr_list</span><span class="hl opt">(</span><span class="hl kwb">int</span> fd<span class="hl opt">,</span> <span class="hl kwb">char</span> <span class="hl opt">**</span>names<span class="hl opt">,</span> <span class="hl kwb">size_t</span> <span class="hl opt">*</span>size<span class="hl opt">) {</span>
ssize_t ret<span class="hl opt">;</span>
<span class="hl kwa">if</span> <span class="hl opt">(*</span>names <span class="hl opt">==</span> NULL <span class="hl opt">&& *</span>size <span class="hl opt">==</span> <span class="hl num">0</span><span class="hl opt">) {</span>
ret <span class="hl opt">=</span> <span class="hl kwd">TEMP_FAILURE_RETRY</span><span class="hl opt">(</span><span class="hl kwd">flistxattr</span><span class="hl opt">(</span>fd<span class="hl opt">,</span> NULL<span class="hl opt">,</span> <span class="hl num">0</span><span class="hl opt">));</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl opt">*</span>size <span class="hl opt">=</span> ret<span class="hl opt">;</span>
<span class="hl opt">*</span>names <span class="hl opt">=</span> <span class="hl kwd">malloc</span><span class="hl opt">(*</span>size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(*</span>names <span class="hl opt">==</span> NULL<span class="hl opt">) {</span>
ret <span class="hl opt">= -</span><span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl opt">}</span>
<span class="hl kwa">for</span> <span class="hl opt">(;;) {</span>
ret <span class="hl opt">=</span> <span class="hl kwd">TEMP_FAILURE_RETRY</span><span class="hl opt">(</span><span class="hl kwd">flistxattr</span><span class="hl opt">(</span>fd<span class="hl opt">, *</span>names<span class="hl opt">, *</span>size<span class="hl opt">));</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt">>=</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl opt">*</span>size <span class="hl opt">=</span> ret<span class="hl opt">;</span>
<span class="hl kwa">break</span><span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>errno <span class="hl opt">!=</span> ERANGE<span class="hl opt">)</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl com">/* New xattr added since first flistxattr */</span>
ret <span class="hl opt">=</span> <span class="hl kwd">realloc_double</span><span class="hl opt">((</span><span class="hl kwb">void</span><span class="hl opt">**)</span>names<span class="hl opt">,</span> size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl opt">}</span>
ret <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
error<span class="hl opt">:</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwb">static int</span> <span class="hl kwd">xattr_get</span><span class="hl opt">(</span><span class="hl kwb">int</span> fd<span class="hl opt">,</span> <span class="hl kwb">const char</span> <span class="hl opt">*</span>name<span class="hl opt">,</span> <span class="hl kwb">void</span> <span class="hl opt">**</span>value<span class="hl opt">,</span> <span class="hl kwb">size_t</span> <span class="hl opt">*</span>size<span class="hl opt">) {</span>
ssize_t ret<span class="hl opt">;</span>
<span class="hl kwa">if</span> <span class="hl opt">(*</span>value <span class="hl opt">==</span> NULL <span class="hl opt">&& *</span>size <span class="hl opt">==</span> <span class="hl num">0</span><span class="hl opt">) {</span>
ret <span class="hl opt">=</span> <span class="hl kwd">TEMP_FAILURE_RETRY</span><span class="hl opt">(</span><span class="hl kwd">fgetxattr</span><span class="hl opt">(</span>fd<span class="hl opt">,</span> name<span class="hl opt">,</span> NULL<span class="hl opt">,</span> <span class="hl num">0</span><span class="hl opt">));</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl opt">*</span>size <span class="hl opt">=</span> ret<span class="hl opt">;</span>
<span class="hl opt">*</span>value <span class="hl opt">=</span> <span class="hl kwd">malloc</span><span class="hl opt">(*</span>size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(*</span>value <span class="hl opt">==</span> NULL<span class="hl opt">) {</span>
ret <span class="hl opt">= -</span><span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl opt">}</span>
<span class="hl kwa">for</span> <span class="hl opt">(;;) {</span>
ret <span class="hl opt">=</span> <span class="hl kwd">TEMP_FAILURE_RETRY</span><span class="hl opt">(</span><span class="hl kwd">flistxattr</span><span class="hl opt">(</span>fd<span class="hl opt">, *</span>value<span class="hl opt">, *</span>size<span class="hl opt">));</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt">>=</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl opt">*</span>size <span class="hl opt">=</span> ret<span class="hl opt">;</span>
<span class="hl kwa">break</span><span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>errno <span class="hl opt">!=</span> ERANGE<span class="hl opt">)</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl com">/* xattr grew since first getxattr */</span>
ret <span class="hl opt">=</span> <span class="hl kwd">realloc_double</span><span class="hl opt">(</span>value<span class="hl opt">,</span> size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> error<span class="hl opt">;</span>
<span class="hl opt">}</span>
ret <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
error<span class="hl opt">:</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwb">static int</span> <span class="hl kwd">str_starts_with</span><span class="hl opt">(</span><span class="hl kwb">const char</span> <span class="hl opt">*</span>s1<span class="hl opt">,</span> <span class="hl kwb">const char</span> <span class="hl opt">*</span>s2<span class="hl opt">) {</span>
<span class="hl kwa">return</span> <span class="hl kwd">strncmp</span><span class="hl opt">(</span>s1<span class="hl opt">,</span> s2<span class="hl opt">,</span> <span class="hl kwd">strlen</span><span class="hl opt">(</span>s2<span class="hl opt">)) ==</span> <span class="hl num">0</span><span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwb">static int</span> <span class="hl kwd">copy_xattrs</span><span class="hl opt">(</span><span class="hl kwb">int</span> srcfd<span class="hl opt">,</span> <span class="hl kwb">int</span> tgtfd<span class="hl opt">) {</span>
ssize_t ret<span class="hl opt">;</span>
<span class="hl kwb">char</span> <span class="hl opt">*</span>names <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
<span class="hl kwb">void</span> <span class="hl opt">*</span>value <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
<span class="hl kwb">size_t</span> names_size <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">,</span> value_size <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">xattr_list</span><span class="hl opt">(</span>srcfd<span class="hl opt">, &</span>names<span class="hl opt">, &</span>names_size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl kwa">for</span> <span class="hl opt">(</span><span class="hl kwb">char</span> <span class="hl opt">*</span>name <span class="hl opt">=</span> names<span class="hl opt">;</span> name <span class="hl opt"><</span> names <span class="hl opt">+</span> names_size<span class="hl opt">;</span>
name <span class="hl opt">=</span> <span class="hl kwd">strchrnul</span><span class="hl opt">(</span>name<span class="hl opt">,</span> <span class="hl str">'\0'</span><span class="hl opt">) +</span> <span class="hl num">1</span><span class="hl opt">) {</span>
<span class="hl com">/* Skip xattrs that need special handling */</span>
<span class="hl kwa">if</span> <span class="hl opt">(!</span><span class="hl kwd">str_starts_with</span><span class="hl opt">(</span>name<span class="hl opt">,</span> <span class="hl str">"user."</span><span class="hl opt">)) {</span>
<span class="hl kwa">continue</span><span class="hl opt">;</span>
<span class="hl opt">}</span>
ret <span class="hl opt">=</span> <span class="hl kwd">xattr_get</span><span class="hl opt">(</span>srcfd<span class="hl opt">,</span> name<span class="hl opt">, &</span>value<span class="hl opt">, &</span>value_size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">TEMP_FAILURE_RETRY</span><span class="hl opt">(</span><span class="hl kwd">fsetxattr</span><span class="hl opt">(</span>tgtfd<span class="hl opt">,</span> name<span class="hl opt">,</span> value<span class="hl opt">,</span> value_size<span class="hl opt">,</span> <span class="hl num">0</span><span class="hl opt">));</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
cleanup<span class="hl opt">:</span>
<span class="hl kwd">free</span><span class="hl opt">(</span>names<span class="hl opt">);</span>
<span class="hl kwd">free</span><span class="hl opt">(</span>value<span class="hl opt">);</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
</pre></div>
<h2>POSIX ACLs</h2>
<p>Feature from a POSIX design specification that wasn't widely adopted,
but Linux supports the draft specification.</p>
<p>Not used much outside of NFS or SAMBA.
You would be forgiven for not knowing they exist.</p>
<p>How they work is beyond the scope of this article,
but <a href="http://man7.org/linux/man-pages/man5/acl.5.html">man7.org</a> covers some details about what it does
and <a href="https://lwn.net/Articles/661357/">lwn.net</a> covers some limitations.</p>
<p>For this article we're not concerned with how to use POSIX ACLs,
it's relevant to us because they work by storing data in a special attribute,
so if we want to preserve this we need to copy <code>system.posix_acl_access</code>.</p>
<p>Since this attribute doesn't start with <code>"user."</code>
we need root privileges to copy it faithfully.</p>
<div class="highlight-c"><pre class="hl"><span class="hl kwb">int</span> <span class="hl kwd">copy_posix_acls</span><span class="hl opt">(</span><span class="hl kwb">int</span> srcfd<span class="hl opt">,</span> <span class="hl kwb">int</span> tgtfd<span class="hl opt">) {</span>
<span class="hl kwb">static const char</span> name<span class="hl opt">[] =</span> <span class="hl str">"system.posix_acl_access"</span><span class="hl opt">;</span>
<span class="hl kwb">int</span> ret <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
<span class="hl kwb">void</span> <span class="hl opt">*</span>value <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
<span class="hl kwb">size_t</span> size <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">xattr_get</span><span class="hl opt">(</span>srcfd<span class="hl opt">,</span> name<span class="hl opt">, &</span>value<span class="hl opt">, &</span>size<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>errno <span class="hl opt">==</span> ENODATA<span class="hl opt">)</span>
ret <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
ret <span class="hl opt">=</span> <span class="hl kwd">TEMP_FAILURE_RETRY</span><span class="hl opt">(</span><span class="hl kwd">fsetxattr</span><span class="hl opt">(</span>tgtfd<span class="hl opt">,</span> name<span class="hl opt">,</span> value<span class="hl opt">,</span> size<span class="hl opt">,</span> <span class="hl num">0</span><span class="hl opt">));</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
cleanup<span class="hl opt">:</span>
<span class="hl kwd">free</span><span class="hl opt">(</span>value<span class="hl opt">);</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
</pre></div>
<p>As with previous articles, the full version of the
<a href="http://yakking.branchable.com/posts/moving-files-6-extended-attributes/my-mv.c">my-mv.c</a> source file and the <a href="http://yakking.branchable.com/posts/moving-files-6-extended-attributes/Makefile">Makefile</a>
may be downloaded.</p>
<h1>Surely that's the last of the metadata!</h1>
<p>I mentioned some extended attributes have special meaning to Linux.
POSIX ACLs can be sanely copied,
but some attributes have tricky semantics that require special handling.</p>