Re: [systemd-devel] [correct PATCH v2] dev-root.device is not active, results in an umount spree

2015-05-19 Thread Martin Pitt
Lennart Poettering [2015-05-18 23:04 +0200]:
 On Mon, 18.05.15 16:08, Martin Pitt (martin.p...@ubuntu.com) wrote:
 
  Martin Pitt [2015-05-17 15:54 +0200]:
   This fixes the original systemd immediately unmounts my mounts bug,
   but not for very long: If you remount or unmount just one mount on a
   tentative device, mountinfo changes, and device_found_node() now calls
   device_update_found_one() with add == 0. Then
   
   n = add ? (d-found | found) : (d-found  ~found);
   
   would unset the previous DEVICE_FOUND_MOUNT flag, leaving 0 (i. e.
   DEVICE_NOT_FOUND). Thus the previously tentative device would once
   again be set to dead, and systemd would unmount all other mounts
   from it. This must never happen, we simply can't declare tentative
   devices as dead and clean up their unmounts, this only works for
   plugged ones that we know via udev.
  
  Eek, I attached the wrong 0002- patch, sorry about that. The above is
  fixed by the attached patch, please ignore 0002- from the previous
  mail. For completeness I also re-attach 0001- again (unchanged).
 
 Still not getting what the purpose of the 0002 patch is, even in this
 revision...
 
 Please elaborate!

I'll try to explain step by step:

Say you are booting with plan9 fs, a container, or some other thing
with a mount that references a device /dev/foo which isn't actually
available as /dev/foo and in udev. Note that you can't reproduce this
in nspawn, as it forcefully mounts /sys as r/o, which triggers the
unit_type_supported(UNIT_DEVICE) safety check in unit_add_node_link();
this happens in environments with writable /sys.

 - Boot, dev-foo.device becomes DEVICE_FOUND_MOUNT/tentative

 - Do some more mounts from /dev/foo, e. g.

   mkdir /tmp/etc /tmp/boot
   mount -o bind /etc /tmp/etc
   mount -o bind /boot /tmp/boot

   (In practice, you'd probably do such bind mounts with nspawn --bind
   or lxc.mount.entry)

   tmp-etc.mount and tmp-boot.mount will be BindsTo=dev-foo.device,
   and dev-foo.device's status is still unchanged
   (DEVICE_FOUND_MOUNT/tentative)

 - Now do umount /tmp/boot. This *also* umounts /tmp/etc!

   Journal says

 systemd[1]: Requested transaction contradicts existing jobs: Resource 
deadlock avoided
 systemd[1]: Unmounted /tmp/etc.

  and dev-sda3.device is now inactive/dead, which tears down
  the bound tmp-etc.mount. Boom!

Reason: Unmounting /tmp/boot triggers
device_update_found_one(found==DEVICE_FOUND_MOUNT, add==0).
d-found previously was DEVICE_FOUND_MOUNT, and this

 n = add ? (d-found | found) : (d-found  ~found);

computes the new state to 0 (i. e. DEVICE_NOT_FOUND), and calls
device_set_state(DEVICE_DEAD). Thus here we (1) don't consider that
dev-foo.device is still bound by other units (tmp-etc.mount) and (2)
lose the information that dev-foo.device is tentative.

So the problem is that this tentative → dead transition only works if
a device is referenced once, but causes overzealous unmounts if there
are more references.

We need a reference count, or check if the device is bound by other
device still. Come to think of it now, we actually already have that:

  Id=dev-foo.device
  BoundBy=tmp-boot.mount tmp-etc.mount

But my patch doesn't take that into account yet.

 Devices that show up in /proc/self/mountinfo or /proc/swap, and then
 disappear again, without ever showing up in udev, need to be cleaned
 up.

That's right, I forgot about that, thanks for catching! So current
master is too overzealous, and my current patch never cleans up. So
this needs some more work.

Thanks,

Martin
-- 
Martin Pitt| http://www.piware.de
Ubuntu Developer (www.ubuntu.com)  | Debian Developer  (www.debian.org)


signature.asc
Description: Digital signature
___
systemd-devel mailing list
systemd-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/systemd-devel


Re: [systemd-devel] [correct PATCH v2] dev-root.device is not active, results in an umount spree

2015-05-18 Thread Martin Pitt
Martin Pitt [2015-05-17 15:54 +0200]:
 This fixes the original systemd immediately unmounts my mounts bug,
 but not for very long: If you remount or unmount just one mount on a
 tentative device, mountinfo changes, and device_found_node() now calls
 device_update_found_one() with add == 0. Then
 
 n = add ? (d-found | found) : (d-found  ~found);
 
 would unset the previous DEVICE_FOUND_MOUNT flag, leaving 0 (i. e.
 DEVICE_NOT_FOUND). Thus the previously tentative device would once
 again be set to dead, and systemd would unmount all other mounts
 from it. This must never happen, we simply can't declare tentative
 devices as dead and clean up their unmounts, this only works for
 plugged ones that we know via udev.

Eek, I attached the wrong 0002- patch, sorry about that. The above is
fixed by the attached patch, please ignore 0002- from the previous
mail. For completeness I also re-attach 0001- again (unchanged).

Martin
-- 
Martin Pitt| http://www.piware.de
Ubuntu Developer (www.ubuntu.com)  | Debian Developer  (www.debian.org)
From bc080c3a0ddd24fabd94d11dc609967d557d044d Mon Sep 17 00:00:00 2001
From: Martin Pitt martin.p...@ubuntu.com
Date: Sun, 17 May 2015 15:07:47 +0200
Subject: [PATCH 1/3] device: create units with intended found value

Change device_found_node() to also create a .device unit if a device is not
known by udev; this is the case for tentative devices picked up by mountinfo
(DEVICE_FOUND_MOUNT).  With that we can record the found attribute on the
unit.

Change device_setup_unit() to also accept a NULL udev_device, and don't
add the extra udev information in that case.

Previously device_found_node() would not create a .device unit, and
unit_add_node_link() would then create a dead stub one via
manager_load_unit(), so we lost the found attribute and unmounted everything
from that device.

https://launchpad.net/bugs/102
http://lists.freedesktop.org/archives/systemd-devel/2015-May/031658.html
---
 src/core/device.c | 53 ++---
 1 file changed, 26 insertions(+), 27 deletions(-)

diff --git a/src/core/device.c b/src/core/device.c
index 8eca4c2..3fddc62 100644
--- a/src/core/device.c
+++ b/src/core/device.c
@@ -292,26 +292,28 @@ static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
 
 static int device_setup_unit(Manager *m, struct udev_device *dev, const char *path, bool main) {
 _cleanup_free_ char *e = NULL;
-const char *sysfs;
+const char *sysfs = NULL;
 Unit *u = NULL;
 bool delete;
 int r;
 
 assert(m);
-assert(dev);
 assert(path);
 
-sysfs = udev_device_get_syspath(dev);
-if (!sysfs)
-return 0;
-
 r = unit_name_from_path(path, .device, e);
 if (r  0)
 return log_error_errno(r, Failed to generate unit name from device path: %m);
 
 u = manager_get_unit(m, e);
 
+if (dev) {
+sysfs = udev_device_get_syspath(dev);
+if (!sysfs)
+return 0;
+}
+
 if (u 
+sysfs 
 DEVICE(u)-sysfs 
 !path_equal(DEVICE(u)-sysfs, sysfs)) {
 log_unit_debug(u, Device %s appeared twice with different sysfs paths %s and %s, e, DEVICE(u)-sysfs, sysfs);
@@ -336,17 +338,19 @@ static int device_setup_unit(Manager *m, struct udev_device *dev, const char *pa
 /* If this was created via some dependency and has not
  * actually been seen yet -sysfs will not be
  * initialized. Hence initialize it if necessary. */
+if (sysfs) {
+r = device_set_sysfs(DEVICE(u), sysfs);
+if (r  0)
+goto fail;
 
-r = device_set_sysfs(DEVICE(u), sysfs);
-if (r  0)
-goto fail;
+(void) device_update_description(u, dev, path);
 
-(void) device_update_description(u, dev, path);
+/* The additional systemd udev properties we only interpret
+ * for the main object */
+if (main)
+(void) device_add_udev_wants(u, dev);
+}
 
-/* The additional systemd udev properties we only interpret
- * for the main object */
-if (main)
-(void) device_add_udev_wants(u, dev);
 
 /* Note that this won't dispatch the load queue, the caller
  * has to do that if needed and appropriate */
@@ -788,22 +792,17 @@ int device_found_node(Manager *m, const char *node, bool add, DeviceFound found,
  * will still have a device node even when the medium
  * is not there... */
 
-if (stat(node, st)  0) {
-if (errno == ENOENT)
+if (stat(node, st) = 0) {
+if (!S_ISBLK(st.st_mode)  !S_ISCHR(st.st_mode))
 

Re: [systemd-devel] [correct PATCH v2] dev-root.device is not active, results in an umount spree

2015-05-18 Thread Lennart Poettering
On Mon, 18.05.15 16:08, Martin Pitt (martin.p...@ubuntu.com) wrote:

 Change device_found_node() to also create a .device unit if a device is not
 known by udev; this is the case for tentative devices picked up by mountinfo
 (DEVICE_FOUND_MOUNT).  With that we can record the found attribute on the
 unit.

Ah, I see now. This makes sense!

  static int device_setup_unit(Manager *m, struct udev_device *dev, const char 
 *path, bool main) {
  _cleanup_free_ char *e = NULL;
 -const char *sysfs;
 +const char *sysfs = NULL;
  Unit *u = NULL;
  bool delete;
  int r;
  
  assert(m);
 -assert(dev);
  assert(path);
  
 -sysfs = udev_device_get_syspath(dev);
 -if (!sysfs)
 -return 0;
 -
  r = unit_name_from_path(path, .device, e);
  if (r  0)
  return log_error_errno(r, Failed to generate unit name from 
 device path: %m);
  
  u = manager_get_unit(m, e);
  
 +if (dev) {
 +sysfs = udev_device_get_syspath(dev);
 +if (!sysfs)
 +return 0;
 +}

Why move this down? In order to keep the patch small and easy to grok,
can this stay up where it was, but simply be enclosed in the if (dev) {} 
check?

  
 -if (stat(node, st)  0) {
 -if (errno == ENOENT)
 +if (stat(node, st) = 0) {
 +if (!S_ISBLK(st.st_mode)  !S_ISCHR(st.st_mode))
  return 0;
  
 -return log_error_errno(errno, Failed to stat device 
 node file %s: %m, node);
 -}
 -
 -if (!S_ISBLK(st.st_mode)  !S_ISCHR(st.st_mode))
 -return 0;
 +dev = udev_device_new_from_devnum(m-udev, 
 S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev);
 +if (!dev  errno != ENOENT)
 +return log_oom();

Hmm, why are all these errors supposed to be OOM?
udev_device_new_from_devnum sets errno correctly, hence we should just
print what it really is set to with log_error_errno(), unless of
course it is ENOENT.

 -dev = udev_device_new_from_devnum(m-udev, 
 S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev);
 -if (!dev) {
 -if (errno == ENOENT)
 -return 0;
 -
 -return log_oom();
 +} else {
 +if (errno != ENOENT)
 +return log_error_errno(errno, Failed to 
 stat device node file %s: %m, node);

The if else { and if (errn... lines could be merged into one 
else if (errno != ..., no?

 From c18dbd432ecd16c57123b5fc04313d625e8a8e88 Mon Sep 17 00:00:00 2001
 From: Martin Pitt martin.p...@ubuntu.com
 Date: Sun, 17 May 2015 15:17:58 +0200
 Subject: [PATCH 2/3] device: never transition from tentative to dead
 
 Only set a device to state dead if it was previously plugged through udev. 
 We
 must never transition from tentative to dead, as there is no way to be 
 sure
 that this is actually true.
 
 This fixes systemd unmounting everything from a tentative device as soon as
 mountinfo changes.

Not following on this patch: what's the rationale here? your patch
basically means we would never ever clean up tentative devices, if
they once were referenced in mountinfo or /proc/swaps, but no longer
are.

Can you elaborate on what this patch is supposed to achieve? How could
it be problematic to deactivate device units that are neither
announced anywhere in /proc nor in udev?

Lennart

-- 
Lennart Poettering, Red Hat
___
systemd-devel mailing list
systemd-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/systemd-devel


Re: [systemd-devel] [correct PATCH v2] dev-root.device is not active, results in an umount spree

2015-05-18 Thread Lennart Poettering
On Mon, 18.05.15 16:08, Martin Pitt (martin.p...@ubuntu.com) wrote:

 Martin Pitt [2015-05-17 15:54 +0200]:
  This fixes the original systemd immediately unmounts my mounts bug,
  but not for very long: If you remount or unmount just one mount on a
  tentative device, mountinfo changes, and device_found_node() now calls
  device_update_found_one() with add == 0. Then
  
  n = add ? (d-found | found) : (d-found  ~found);
  
  would unset the previous DEVICE_FOUND_MOUNT flag, leaving 0 (i. e.
  DEVICE_NOT_FOUND). Thus the previously tentative device would once
  again be set to dead, and systemd would unmount all other mounts
  from it. This must never happen, we simply can't declare tentative
  devices as dead and clean up their unmounts, this only works for
  plugged ones that we know via udev.
 
 Eek, I attached the wrong 0002- patch, sorry about that. The above is
 fixed by the attached patch, please ignore 0002- from the previous
 mail. For completeness I also re-attach 0001- again (unchanged).

Still not getting what the purpose of the 0002 patch is, even in this
revision...

Please elaborate!

 --- a/src/core/device.c
 +++ b/src/core/device.c
 @@ -465,12 +465,13 @@ static void device_update_found_one(Device *d, bool 
 add, DeviceFound found, bool
   * now referenced by the kernel, then we assume the
   * kernel knows it now, and udev might soon too. */
  device_set_state(d, DEVICE_TENTATIVE);
 -else
 -/* If nobody sees the device, or if the device was
 - * previously seen by udev and now is only referenced
 - * from the kernel, then we consider the device is
 +else if (previous  DEVICE_FOUND_UDEV)
 +/* If the device was previously seen by udev and now is only
 + * referenced from the kernel, then we consider the device is
   * gone, the kernel just hasn't noticed it yet. */
  device_set_state(d, DEVICE_DEAD);
 +/* We never move from TENTATIVE to DEAD, as we can only determine 
 this
 + * status update with udev, not with mountinfo */
  }

Devices that show up in /proc/self/mountinfo or /proc/swap, and then
disappear again, without ever showing up in udev, need to be cleaned
up.

Lennart

-- 
Lennart Poettering, Red Hat
___
systemd-devel mailing list
systemd-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/systemd-devel