pages tagged security
yakking
http://yakking.branchable.com/tags/security/
yakking
ikiwiki
2016-10-19T11:00:13Z
How difficult is it to preserve extended attributes when moving a file?
http://yakking.branchable.com/posts/moving-files-7-difficult-xattrs/
Richard Maw
2016-10-19T11:00:13Z
2016-10-19T11:00:06Z
<h1>Dealing with semantically important <a href="http://man7.org/linux/man-pages/man5/attr.5.html">xattrs</a></h1>
<p>We previously spoke about <a href="http://man7.org/linux/man-pages/man5/attr.5.html">extended attributes</a>
like they were just another piece of metadata attached to files.</p>
<p>However some have rather awkward interfaces as far as copying is concerned,
some because they don't depend on the file's contents itself,
and some because they are filesystem specific.</p>
<h2>Selinux labels</h2>
<p>Selinux is a complicated mandatory access control mechanism.</p>
<p>Rather than store the access control rules in the file,
like POSIX ACLs do,
the rules are stored elsewhere in the kernel
and a reference to what kind of file it is,
is stored in the file as an extended attribute
called the "security label".</p>
<p>The security label and the security context of the process accessing the file
are looked up in the ACL rules in the kernel
to determine whether the operation is permitted.</p>
<p>The details of how to define Selinux rules is complicated
and beyond the scope of this article.
We only care how we should reapply the rules when moving the file.</p>
<p>While we could copy the label from the old file into the new file,
as we did for POSIX ACLs,
Selinux contexts are defined by their file paths rather than the inodes,
so after we move a file we should relabel it
to what the file should have in the new location.</p>
<p>Using <a href="http://man7.org/linux/man-pages/man3/selinux_restorecon.3.html">selinux_restorecon(3)</a> might be tempting,
but it leaves open a race condition
where the file would be created with the wrong context
so it temporarily accessible with the wrong label.</p>
<p>If the file context should be preserved from the original file,
then you must read the context from the extended attribute,
either directly with <a href="http://man7.org/linux/man-pages/man2/fgetxattr.2.html">fgetxattr(2)</a> or <a href="http://man7.org/linux/man-pages/man3/fgetfilecon.3.html">fgetfilecon(3)</a>,
and then set the context before creating the new file
with <a href="http://man7.org/linux/man-pages/man3/setfscreatecon.3.html">setfscreatecon(3)</a>.</p>
<p>If instead it should have the label that the path database says it should be,
then the required context can be found by using <a href="http://man7.org/linux/man-pages/man3/selabel_open.3.html">selabel_open(3)</a>
with <code>SELABEL_CTX_FILE</code> to get a reference to the file contexts database,
then getting the label it should have at that path using <a href="http://man7.org/linux/man-pages/man3/selabel_lookup.3.html">selabel_lookup(3)</a>,
and setting the context for new files with <a href="http://man7.org/linux/man-pages/man3/setfscreatecon.3.html">setfscreatecon(3)</a>.</p>
<p>Existing files can have their labels changed with <a href="http://man7.org/linux/man-pages/man3/selinux_restorecon.3.html">selinux_restorecon(3)</a>.</p>
<p>The <a href="http://man7.org/linux/man-pages/man3/setfscreatecon.3.html">setfscreatecon(3)</a> API is unfortunate as it involves global state.
Recent enough versions of Linux have the <code>O_TMPFILE</code> flag for file creation,
which doesn't create a directory entry for the file when it is created,
so you can modify the file before it is visible to other processes,
and can be bound into place with <a href="http://man7.org/linux/man-pages/man2/link.2.html">linkat(2)</a>.</p>
<div class="highlight-c"><pre class="hl"><span class="hl kwb">int</span> <span class="hl kwd">set_selinux_create_context</span><span class="hl opt">(</span><span class="hl kwb">const char</span> <span class="hl opt">*</span>tgt<span class="hl opt">,</span> mode_t srcmode<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">struct</span> selabel_handle <span class="hl opt">*</span>hnd <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
<span class="hl kwb">char</span> <span class="hl opt">*</span>context <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
hnd <span class="hl opt">=</span> <span class="hl kwd">selabel_open</span><span class="hl opt">(</span>SELABEL_CTX_FILE<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>hnd <span class="hl opt">==</span> NULL<span class="hl opt">) {</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>errno <span class="hl opt">!=</span> ENOENT<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 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">selabel_lookup</span><span class="hl opt">(</span>hnd<span class="hl opt">, &</span>context<span class="hl opt">,</span> tgt<span class="hl opt">,</span> srcmode<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>
ret <span class="hl opt">=</span> <span class="hl kwd">setfscreatecon</span><span class="hl opt">(</span>context<span class="hl opt">);</span>
cleanup<span class="hl opt">:</span>
<span class="hl kwd">freecon</span><span class="hl opt">(</span>context<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>hnd <span class="hl opt">!=</span> NULL<span class="hl opt">)</span>
<span class="hl kwd">selabel_close</span><span class="hl opt">(</span>hnd<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><a href="https://www.kernel.org/doc/Documentation/security/Smack.txt">SMACK</a></h2>
<p>This is another security technology.</p>
<p>Like Selinux it has labels.
These are stored in extended attributes matching <code>security.SMACK64*</code>,
so require root privileges to copy faithfully.</p>
<h2>btrfs flags</h2>
<p>Only worth copying if both source and destination are on btrfs,
but if you then move a file back to btrfs you might want to restore them.</p>
<p>The only flag of real interest is <code>"btrfs.compression"</code>,
which is safe to ignore if moving to a file system which doesn't support it.</p>
<p>A "brain dead" implementation for this and SMACK is to check the prefix,
and silently accept failure if setting the attribute fails.</p>
<div class="highlight-c"><pre class="hl"><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 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">"security.SMACK64"</span><span class="hl opt">) &&</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">"btrfs."</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">if</span> <span class="hl opt">(</span>errno <span class="hl opt">==</span> EINVAL <span class="hl opt">&&</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">"security.SMACK64"</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">"btrfs."</span><span class="hl opt">))) {</span>
<span class="hl kwa">continue</span><span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<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>
<p>As with previous articles, the full version of the
<a href="http://yakking.branchable.com/posts/moving-files-7-difficult-xattrs/my-mv.c">my-mv.c</a> source file and the <a href="http://yakking.branchable.com/posts/moving-files-7-difficult-xattrs/Makefile">Makefile</a>
may be downloaded.</p>
<p>The <a href="http://yakking.branchable.com/posts/moving-files-7-difficult-xattrs/Makefile">Makefile</a> has changed since earlier
since it now needs to link against libselinux.</p>
<h1>So now we've got an equivalent to a slow <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>, right?</h1>
<p>Not quite, <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a> is atomic.
It disappears from the old location and reappears whole at the new one at the same time.</p>
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>