Bug 80135 - gdbus fails to send three or more values; io-error
Summary: gdbus fails to send three or more values; io-error
Status: RESOLVED NOTOURBUG
Alias: None
Product: dbus
Classification: Unclassified
Component: core (show other bugs)
Version: unspecified
Hardware: Other All
: medium normal
Assignee: Havoc Pennington
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2014-06-17 13:51 UTC by Que Quotion
Modified: 2014-06-20 19:06 UTC (History)
0 users

See Also:
i915 platform:
i915 features:


Attachments

Description Que Quotion 2014-06-17 13:51:58 UTC
I seem to recall having the same problem with dbus-send quite a long time ago. If i recall correctly, the problem was that sending three arguments or more is either broken or not allowed (and not documented as such).

Here's an example:

org.freedesktop.login1.Manager.KillSession (from systemd-logind) wants string(sessionid) string(leader||all) uint32(signal) but throws an IO error when given them:

Error: GDBus.Error:org.freedesktop.DBus.Error.IOError: Input/output error
(According to introspection data, you need to pass 'ssi')

Here's a bash line that will produce exactly this problem and prove that the problem is not the input (note use of sudo, ordinary users cannot KillSession):

SESSIONID=$(gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.GetSessionByPID $$ | sed -nr '/.*session\/(.{,2}).*/s//\1/p'); sudo gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.KillSession $SESSIONID leader 15; printf "\nFailed to kill session: $SESSIONID\n\nOf available sessions:\n"; loginctl list-sessions
Comment 1 Simon McVittie 2014-06-17 14:43:05 UTC
I'm not sure exactly what you're trying to achieve here - do you really want to terminate the session you're in? - but using gdbus, sed and shell-script seems unlikely to be the best approach to it. I would recommend using systemd's own libraries via C or any language that wraps them, using the GDBus C API (part of libgio) via C, python-gi, gjs or seed, or using libdbus via C or dbus-python.

D-Bus does not have any hard-coded limitation to <= 2 arguments. I would be extremely surprised if any D-Bus implementation had that limitation either.

(In reply to comment #0)
> I seem to recall having the same problem with dbus-send quite a long time
> ago.

The reference implementation of D-Bus does not include gdbus (although it does include dbus-send) so this is not our bug, but for what it's worth, your shell script appears to be wrong.

> Here's a bash line that will produce exactly this problem and prove that the
> problem is not the input (note use of sudo, ordinary users cannot
> KillSession):
> 
> SESSIONID=$(gdbus call -y -d org.freedesktop.login1 -o
> /org/freedesktop/login1 -m org.freedesktop.login1.Manager.GetSessionByPID $$
> | sed -nr '/.*session\/(.{,2}).*/s//\1/p')

With the version of logind I'm running, this outputs _3, parsed from "(objectpath '/org/freedesktop/login1/session/_34',)". However, my login session ID is actually 4 (systemd has escaped it using a reversible encoding: 0x34 is the ASCII character 4).

In general, unless a D-Bus API has explicitly documented how to parse its object paths, you should assume that they are opaque.

You can get the session ID from a session object-path using org.freedesktop.DBus.Properties.Get for the Id property on the org.freedesktop.login1.Session interface. Using something that isn't a shell script would make this considerably easier and more robust.

In this case, you don't actually need to parse the object path, because you can call Kill on the object reprsented by that path, something like:

    sudo gdbus call -y -d org.freedesktop.login1 \
        -o /org/freedesktop/login1/session/_34 \
        -m org.freedesktop.login1.Session.Kill "'leader'" 15

although in my version of logind the signature of Kill() seems to have changed to take two strings, so for me it'd be more like:

    sudo gdbus call -y -d org.freedesktop.login1 \
        -o /org/freedesktop/login1/session/_34 \
        -m org.freedesktop.login1.Session.Kill "'leader'" "'15'"

(see below for explanation of the quoting)

> sudo gdbus call -y -d
> org.freedesktop.login1 -o /org/freedesktop/login1 -m
> org.freedesktop.login1.Manager.KillSession $SESSIONID leader 15

gdbus expects its arguments to be in stringified GVariant syntax. The GVariant syntax for a string is for it to be single- or double-quoted, and to protect that quoting from the shell you need to quote it again, something like:

gdbus call [...] "'$SESSIONID'" "'leader'" 15

... but again, using something that isn't a shell script would make this considerably easier and more robust.

The equivalent for dbus-send is something like string:$SESSIONID string:leader int32:15.
Comment 2 Que Quotion 2014-06-18 20:18:45 UTC
Reopening bug, which is a bug (in gdbus if not dbus-core). Please do not close it until the IO error when giving three or more arguments can be resolved or disproven.

>I'm not sure exactly what you're trying to achieve here - do you really want to terminate the session you're in?

Yes, that is exacty what I want to do. See this thread for an explanation: https://bbs.archlinux.org/viewtopic.php?id=181666

The point is to use systemd-logind to make a clean logout from the currently logged in session ("clean" meaning without affecting other sessions, even those owned by the same user such as tty sessions)

>using gdbus

Since the only way to get the Session ID is through dbus, I decided to stick with it for my systemd/logind session control script.

>sed

I'll come back to this; you've confirmed my suspicions about another serious problem.

>shell-script

A means to an end, which should not be a problem.

>The reference implementation of D-Bus does not include gdbus

Couldn't find a place to report bugs on gdbus, and remembered having this very same problem with dbus-send (can't find any record of it) so I suspect the problem is either internal to dbus or somewhere between utilities like dbus-send/gdbus and dbus itself.

>your shell script appears to be wrong.

It isn't, but you did bring up an important point regarding the sed bit and session ids.

>With the version of logind I'm running, this outputs _3, parsed from "(objectpath '/org/freedesktop/login1/session/_34',)". However, my login session ID is actually 4 (systemd has escaped it using a reversible encoding: 0x34 is the ASCII character 4).

Your session ID is actually "_34", not 4; regardless of what the number means, the string by which your session can be identified is "_34". This confirms a problem I suspected; the nature of session IDs is unknowable (how many characters they are, what characters are valid, etc).

In general, unless a D-Bus API has explicitly documented how to parse its object paths, you should assume that they are opaque.

>You can get the session ID from a session object-path using org.freedesktop.DBus.Properties.Get for the Id property on the org.freedesktop.login1.Session interface.

You are assuming something that is impossible: knowing what the current session ID is. The only way to get the session ID is to get the session object-path first, which is why I'm using GetSessionByPID and sed to strip the unnecessary information (which is difficult, because session IDs cannot be pattern-matched as they are unknowable). There simply are no functions or variables that output the current session ID.

Please check the output of: gdbus introspect -y -d org.freedesktop.login1 -o /org/freedesktop/login1/

There are no fuctions or variables that simply return the session id; the only similar thing is a nonsense function of Manager called GetSession, which takes an argument: string(sessionID). Disregarding it's own name and any perceivable logic, this function does not output the session ID, but requires that one input the session ID in order to output an object path for that session (should be called GetSessionPath).

This brings me back to the point about sed: as I suspected, session ID strings are an unknowable quantity. For you, they appear to be three-character strings begining with an underscore; for me they are consistently two-character strings begining with the letter "c" (hence my sed line only picks up the last two characters of the object path; I tried to get all the characters between "session/" and the final ")" but I could never make it work).

>although in my version of logind the signature of Kill() seems to have changed to take two strings, so for me it'd be more like:

This is not the same fuction, nothing has changed; it is an entirely different method of doing something similar to what I proposed (one that is completely undocumented as well, how do you even know about this?)

>gdbus expects its arguments to be in stringified GVariant syntax. The GVariant syntax for a string is for it to be single- or double-quoted, and to protect that quoting from the shell you need to quote it again, something like:

No, this is entirely unnecessary. No special quotation whatsoever is required to input strings, nor does using any make any difference for this bug.

Replace my "c5" session ID (a tty session in this case) with one of your "_##" session IDs and try this (no quotations at all):

gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.GetSession c5

Then try any of these (and notice identical error messages):

gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.KillSession c5 leader 15
Error: GDBus.Error:org.freedesktop.DBus.Error.IOError: Input/output error
(According to introspection data, you need to pass 'ssi')

gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.KillSession 'c5' 'leader' 15
Error: GDBus.Error:org.freedesktop.DBus.Error.IOError: Input/output error
(According to introspection data, you need to pass 'ssi')

gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.KillSession "'c5'" "'leader'" 15
Error: GDBus.Error:org.freedesktop.DBus.Error.IOError: Input/output error
(According to introspection data, you need to pass 'ssi')

As you can see, the problem is that gdbus fails when trying to send these arguments. It reminds me of a problem I had with dbus-send a long time ago, in which I recall three or more arguments caused an IO error. I suspect it is the same problem; caused by something broken in both gdbus and dbus-send (unlikely, but possible), something broken between them and dbus, or something broken in dbus.
Comment 3 Simon McVittie 2014-06-19 11:38:29 UTC
(In reply to comment #2)
> Reopening bug, which is a bug (in gdbus if not dbus-core). Please do not
> close it until the IO error when giving three or more arguments can be
> resolved or disproven.

This is the bug tracker for the reference implementation. gdbus is part of GLib, whose bugs are tracked on the GNOME bug tracker.

> This
> confirms a problem I suspected; the nature of session IDs is unknowable (how
> many characters they are, what characters are valid, etc).

This is not a bug in D-Bus, which has no concept of session IDs. Complain to the authors of systemd-logind if you don't like its D-Bus API.

It also isn't true:

> >You can get the session ID from a session object-path using org.freedesktop.DBus.Properties.Get for the Id property on the org.freedesktop.login1.Session interface.
> 
> You are assuming something that is impossible: knowing what the current
> session ID is.

It is entirely possible. Get the object path of the current session object via GetSessionByPID; that gives you the path to a session object. That session object implements the Properties API, and one of its properties is the simple string session ID:

% gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.GetSessionByPID $$
(objectpath '/org/freedesktop/login1/session/_34',)
% gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1/session/_34 -m org.freedesktop.DBus.Properties.Get '"org.freedesktop.login1.Session"' '"Id"'       
(<'4'>,)

The text format here (GVariant serialization) is not particularly convenient for shell scripting, but it wasn't my choice to use shell script for this. If you use an object-oriented language, you'll get objects instead.

> There are no fuctions or variables that simply return the session id; the
> only similar thing is a nonsense function of Manager called GetSession,
> which takes an argument: string(sessionID).

systemd-logind's API seems somewhat confused about whether sessions are identified by a short string or by an object path - if I designed it, I'd have used the object paths consistently - but that isn't D-Bus' fault. If you object, take it up with the people who designed the systemd-logind API.

> This is not the same fuction, nothing has changed; it is an entirely
> different method of doing something similar to what I proposed (one that is
> completely undocumented as well, how do you even know about this?)

I checked what API the session object provides:

% gdbus introspect -y -d org.freedesktop.login1 -o /org/freedesktop/login1/session/_34

It has a method named Kill on the Session interface. It seems reasonable to expect that that does the same thing as KillSession.

If I'm wrong and Kill doesn't do the same thing as KillSession, then that would be a confusing design choice in systemd-logind, but still not a bug in D-Bus.

> No, this is entirely unnecessary. No special quotation whatsoever is
> required to input strings, nor does using any make any difference for this
> bug.

I was wrong about that part: gdbus(1) says "strings do not need explicit quotes". I'm not sure how it can disambiguate ambiguous situations like that, but whatever...

> Replace my "c5" session ID (a tty session in this case) with one of your
> "_##" session IDs and try this (no quotations at all):
> 
> gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m
> org.freedesktop.login1.Manager.GetSession c5

My session ID is not _34, it is 4. The version of it encoded into the object-path is _34.

Think of it like this: if the session ID is the name of a file available on a web server, like "read me.txt", then the object path is like the path assigned to that file, like "/docs/misc/read%20me.txt" in the URL "http://example.com/docs/misc/read%20me.txt" (note the encoding, because spaces aren't valid in URLs). In this case, systemd-logind is avoiding placing a digit immediately after a separator. The hypothetical file is still called "read me.txt" and my session is still called 4.

So, this works for me, and does the reverse of the Get() call I quoted above:

% gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.GetSession 4 
(objectpath '/org/freedesktop/login1/session/_34',)

For you, the session ID is c1 or something; for me it's 4.

> Then try any of these (and notice identical error messages):
> 
> gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m
> org.freedesktop.login1.Manager.KillSession c5 leader 15
> Error: GDBus.Error:org.freedesktop.DBus.Error.IOError: Input/output error
> (According to introspection data, you need to pass 'ssi')

My local version of systemd-logind appears to have a bug where the introspection data used by gdbus thinks KillSession takes (string, string, string), whereas it really takes (string, string, int), so gdbus can't be used with it. That's nothing to do with the number of arguments, or the IOError, and appears to be fixed in your systemd version in any case.

The "Input/output error" is most likely coming from systemd-logind itself, perhaps indicating that it, or systemd, has reported EIO somewhere. It's unhelpful that it uses errno-style error mappings and doesn't provide any context in the human-readable message; D-Bus APIs usually provide better error messages than that.

gdbus can certainly call functions with 3 or more arguments, and so can dbus-send. The Inhibit method has 4 "in" arguments (plus one "out" argument):

% sudo gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.Inhibit "'sleep'" "'a test of gdbus'" "'because I say so'" "'delay'"
(handle 0,)
% sudo dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager.Inhibit string:sleep string:"a test of dbus-send" string:"because I say so" string:delay
method return sender=:1.3 -> dest=:1.652 reply_serial=2
   unix fd 5

and having started an expendable session, which was given session ID 452, I can kill it with KillSession:

% sudo dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager.KillSession string:452 string:leader int32:15
method return sender=:1.3 -> dest=:1.653 reply_serial=2

(and the session has indeed died, although you can't see that here.)
Comment 4 Que Quotion 2014-06-20 17:59:17 UTC
I'm satisfied this cannot be fixed in dbus-core; perhaps it should be refiled against glib.
Comment 5 Que Quotion 2014-06-20 19:06:10 UTC
>This is the bug tracker for the reference implementation. gdbus is part of GLib, whose bugs are tracked on the GNOME bug tracker.

Good to know; somehow I couldn't get that information out of Google.

>This is not a bug in D-Bus, which has no concept of session IDs. Complain to the authors of systemd-logind if you don't like its D-Bus API.

I didn't mean to imply that it was. Indeed I have several complaints for the authors of systemd-logind and not only regarding its D-Bus API.

>It is entirely possible. Get the object path of the current session object via GetSessionByPID; that gives you the path to a session object. That session object implements the Properties API, and one of its properties is the simple string session ID:

>% gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.GetSessionByPID $$
(objectpath '/org/freedesktop/login1/session/_34',)
>% gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1/session/_34 -m org.freedesktop.DBus.Properties.Get '"org.freedesktop.login1.Session"' '"Id"'       
(<'4'>,)


Thank you for this. It is better than my initial GetSessionByPID & sed approach, which would be broken in the case that the session id as listed in the object path were not the actual session id (as in your case with "_34" instead of "4", which I find bewildering--whose idea was that?).

Still, I do wish there was a way to print the current session ID as a string with no unrelated characters from Manager() (something like GetCurrentSessionID(null)), but that's one of the issues that I'll have to take up with the systemd-logind people.

>systemd-logind's API seems somewhat confused about whether sessions are identified by a short string or by an object path - if I designed it, I'd have used the object paths consistently - but that isn't D-Bus' fault. If you object, take it up with the people who designed the systemd-logind API.

Yeah, I might just do that.

>I checked what API the session object provides:

>% gdbus introspect -y -d org.freedesktop.login1 -o /org/freedesktop/login1/session/_34


It did not occur to me to do this; I am not so familiar with the inner workings of dbus.

>It has a method named Kill on the Session interface. It seems reasonable to expect that that does the same thing as KillSession.

It probably does, I was just pointing out that it isn't KillSession. I'd prefer to use org.freedesktop.login1.Manager for the logout function just like it can be used for Suspend, Hibernate, Restart, and Shutdown because it would make for simpler coding (if it worked and didn't require a password...). I haven't tested it yet, but it's slated for the next version of my systemd-dbus-session-manager script in combination with your suggested way of getting the session ID, with improved sed magic (perhaps I can grab whatever is inside the single quotes), 

>My session ID is not _34, it is 4. The version of it encoded into the object-path is _34.

Again, bewildering; whose idea was it not to use the actual session id in the object path... that just makes it one step more difficult (in shell) to reliably acquire the session id:

-No functions output the current session id, so get the session's object path from GetSesssionByPID($$)
-The session name as it appears in the object path may not be the actual sesssion ID, so don't bother extracting it from there. Instead, extract the object path (sed to get everything between the single quotes) to get the real session id from org.freedesktop.DBus.Properties.Get
-Extract the session id from that output (sed to get everything between the single quotes)

>Think of it like this: if the session ID is the name of a file available on a web server, like "read me.txt", then the object path is like the path assigned to that file, like "/docs/misc/read%20me.txt" in the URL "http://example.com/docs/misc/read%20me.txt" (note the encoding, because spaces aren't valid in URLs). In this case, systemd-logind is avoiding placing a digit immediately after a separator. The hypothetical file is still called "read me.txt" and my session is still called 4.

But why does systemd escape your session id at all? Why not simply use "4" instead of obfuscating the session id with the ASCII character reference for it? And why do our different versions of systemd-logind use different naming schemes for sessions? (issues to take up with the systemd-logind devteam)

>The "Input/output error" is most likely coming from systemd-logind itself, perhaps indicating that it, or systemd, has reported EIO somewhere. It's unhelpful that it uses errno-style error mappings and doesn't provide any context in the human-readable message; D-Bus APIs usually provide better error messages than that.

Not a very helpful message at all. In fact, I'm fairly certain the blame will be arbitrarily shifted elsewhere if I attempt to bring this to another team without more verbose output that could show exactly where the IO error occurs (on the way out of gdbus, on the way through glib, on the way into dbus, in the actual KillSession method itself, somewhere else, etc).

>gdbus can certainly call functions with 3 or more arguments, and so can dbus-send. The Inhibit method has 4 "in" arguments (plus one "out" argument):

>% sudo gdbus call -y -d org.freedesktop.login1 -o /org/freedesktop/login1 -m org.freedesktop.login1.Manager.Inhibit "'sleep'" "'a test of gdbus'" "'because I say so'" "'delay'"

Indeed this works. I can't find any record of the problem I had with dbus-send, although I don't recall it being resolved I also don't recall how I came across that particular issue in the first place. Perhaps it was resolved; perhaps my memory isn't reliable.

>and having started an expendable session, which was given session ID 452, I can kill it with KillSession:

>% sudo dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager.KillSession string:452 string:leader int32:15 method return sender=:1.3 -> dest=:1.653 reply_serial=2

This does not work for me:

sudo dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager.KillSession string:c3 string:leader int32:15 method return sender=:1.3 -> dest=:1.653 reply_serial=2
dbus-send: Data item "method" is badly formed

Thank you for the very informative reply!


Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.