Bug 895905

Summary: guestmount: link() incorrectly returns ENOENT, when it should be EXDEV
Product: [Community] Virtualization Tools Reporter: Richard W.M. Jones <rjones>
Component: libguestfsAssignee: Richard W.M. Jones <rjones>
Status: CLOSED UPSTREAM QA Contact:
Severity: unspecified Docs Contact:
Priority: unspecified    
Version: unspecifiedCC: bfan, dyasny, leiwang, mbooth, moli, qguan, walters, wshi
Target Milestone: ---   
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: 892291 Environment:
Last Closed: 2013-01-21 09:46:13 EST Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: ---
Bug Depends On:    
Bug Blocks: 892291    
Attachments:
Description Flags
test script
none
API-level link test none

Description Richard W.M. Jones 2013-01-16 04:06:17 EST
+++ This bug was initially created as a clone of Bug #892291 +++

Description of problem:

My project ostree uses hardlinks extensively; I have a bit of code which tries link() and falls back to a copy if it gets EXDEV.

Unfortunately, something in the guestmount/fuse/kernel chain is giving me ENOENT, when it should be EXDEV.  I've added a workaround to ostree, but this is suboptimal.

Version-Release number of selected component (if applicable):

libguestfs-1.16.34-2.el6.x86_64
fuse-2.8.3-4.el6.x86_64
kernel-2.6.32-350.el6.x86_64


How reproducible:

Always.

Steps to Reproduce:
1. guestmount --rw -o allow_root --pid-file guestmount.pid -a test.img -m /dev/sda3 -m /dev/sda1:/boot mnt
2. cd mnt
3. touch foo
4. ln foo boot/foo
 
Actual results:

ENOENT

Expected results:

EXDEV

--- Additional comment from RHEL Product and Program Management on 2013-01-06 06:13:09 EST ---

Since this bug report was entered in bugzilla, the release flag has been
set to ? to ensure that it is properly evaluated for this release.

--- Additional comment from RHEL Product and Program Management on 2013-01-11 01:47:31 EST ---

This request was not resolved in time for the current release.
Red Hat invites you to ask your support representative to
propose this request, if still desired, for consideration in
the next release of Red Hat Enterprise Linux.
Comment 1 Richard W.M. Jones 2013-01-16 04:07:26 EST
Cloning this bug to check whether it happens upstream too.
Comment 2 Richard W.M. Jones 2013-01-21 08:15:11 EST
Created attachment 684279 [details]
test script

Simple self-contained test script demonstrating the problem.

The output is:

  [...]
  + mkdir -p /tmp/mnt
  + guestmount -a test1.img -m /dev/sda1:/ -m /dev/sda2:/boot /tmp/mnt
  + touch /tmp/mnt/foo
  + cd /tmp/mnt
  + ln foo boot/foo
  ln: failed to create hard link ‘boot/foo’ => ‘foo’: No such file or directory

(The error message should be "Invalid cross-device link")
Comment 3 Richard W.M. Jones 2013-01-21 08:22:50 EST
Created attachment 684284 [details]
API-level link test

This is a test script to see what the guestfs_ln API
returns.

Note that the current implementation of guestfs_ln runs
the external 'ln' binary.  This has two obvious problems:

(a) it's slow

(b) it doesn't capture errno

There's no obvious reason why we could not replace this
impl with a direct call to link(2).
Comment 4 Richard W.M. Jones 2013-01-21 09:46:13 EST
Two commits are needed to fix this fully:

https://github.com/libguestfs/libguestfs/commit/533082e28240522df51e6615d99794d726c8e1f0

https://github.com/libguestfs/libguestfs/commit/28e34290ff0dc11e9c5d8b8a1e5992cd0cc941fb

The first commit changes guestfs_ln to use link(2) directly
instead of the external 'ln' program, ensuring that we can
capture the errno.  That commit on its own does fix the whole
bug (including guestmount), but ...

When I looked closely at the code to guestmount to try to work
out why ENOENT was being returned (ENOENT appears nowhere along
any relevant codepaths), I realized there is a second bug in
guestmount which affects lots of things.  The current guestmount
code returns errnos to FUSE by just doing:

  return -guestfs_last_errno(g);

That works, in the cases where the previous guestfs_* API call
captured the errno.  In other cases, guestfs_last_errno returns
0, and thus 0 (no error) is passed back to FUSE.

Ooops.

What was in fact happening in the ln-over-guestmount case was that
the link call was *succeeding*, but a subsequent open was failing
with ENOENT.  The second commit fixes this.