Skip to content

Commit c58196e

Browse files
committed
Set up new VT for Wayland sessions
Introduce the VirtualTerminal namespace that contains utility functions to set up VT. Leak tty into the session as stdin so it stays open without races. The user session process also need to be the session leader and take control of the tty. Issue: #419
1 parent 6a0e0f7 commit c58196e

File tree

6 files changed

+257
-4
lines changed

6 files changed

+257
-4
lines changed

src/daemon/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set(DAEMON_SOURCES
2424
SeatManager.cpp
2525
SignalHandler.cpp
2626
SocketServer.cpp
27+
VirtualTerminal.cpp
2728
)
2829

2930
qt5_add_dbus_adaptor(DAEMON_SOURCES "${CMAKE_SOURCE_DIR}/data/interfaces/org.freedesktop.DisplayManager.xml" "DisplayManager.h" SDDM::DisplayManager)

src/daemon/Display.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "Greeter.h"
3131
#include "Utils.h"
3232
#include "SignalHandler.h"
33+
#include "VirtualTerminal.h"
3334

3435
#include <QDebug>
3536
#include <QFile>
@@ -260,14 +261,21 @@ namespace SDDM {
260261
// some information
261262
qDebug() << "Session" << m_sessionName << "selected, command:" << session.exec();
262263

264+
// create new VT for Wayland sessions otherwise use greeter vt
265+
int vt = terminalId();
266+
if (session.xdgSessionType() == QStringLiteral("wayland")) {
267+
vt = VirtualTerminal::setUpNewVt();
268+
VirtualTerminal::jumpToVt(vt);
269+
}
270+
263271
QProcessEnvironment env;
264272
env.insert("PATH", mainConfig.Users.DefaultPath.get());
265273
if (session.xdgSessionType() == QStringLiteral("x11"))
266274
env.insert("DISPLAY", name());
267275
env.insert("XDG_SEAT", seat()->name());
268276
env.insert("XDG_SEAT_PATH", daemonApp->displayManager()->seatPath(seat()->name()));
269277
env.insert("XDG_SESSION_PATH", daemonApp->displayManager()->sessionPath(QString("Session%1").arg(daemonApp->newSessionId())));
270-
env.insert("XDG_VTNR", QString::number(terminalId()));
278+
env.insert("XDG_VTNR", QString::number(vt));
271279
env.insert("DESKTOP_SESSION", session.desktopSession());
272280
env.insert("XDG_CURRENT_DESKTOP", session.desktopNames());
273281
env.insert("XDG_SESSION_CLASS", "user");

src/daemon/VirtualTerminal.cpp

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/***************************************************************************
2+
* Copyright (c) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the
16+
* Free Software Foundation, Inc.,
17+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
***************************************************************************/
19+
20+
#include <QDebug>
21+
#include <QString>
22+
23+
#include "VirtualTerminal.h"
24+
25+
#include <unistd.h>
26+
#include <fcntl.h>
27+
#include <signal.h>
28+
#include <linux/vt.h>
29+
#include <linux/kd.h>
30+
#include <sys/ioctl.h>
31+
32+
#define RELEASE_DISPLAY_SIGNAL (SIGRTMAX)
33+
#define ACQUIRE_DISPLAY_SIGNAL (SIGRTMAX - 1)
34+
35+
namespace SDDM {
36+
namespace VirtualTerminal {
37+
static void onAcquireDisplay(int signal) {
38+
int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
39+
ioctl(fd, VT_RELDISP, VT_ACKACQ);
40+
close(fd);
41+
}
42+
43+
static void onReleaseDisplay(int signal) {
44+
int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
45+
ioctl(fd, VT_RELDISP, 1);
46+
close(fd);
47+
}
48+
49+
static bool handleVtSwitches(int fd) {
50+
vt_mode setModeRequest = { 0 };
51+
bool ok = true;
52+
53+
setModeRequest.mode = VT_PROCESS;
54+
setModeRequest.relsig = RELEASE_DISPLAY_SIGNAL;
55+
setModeRequest.acqsig = ACQUIRE_DISPLAY_SIGNAL;
56+
57+
if (ioctl(fd, VT_SETMODE, &setModeRequest) < 0) {
58+
qDebug() << "Failed to manage VT manually:" << strerror(errno);
59+
ok = false;
60+
}
61+
62+
signal(RELEASE_DISPLAY_SIGNAL, onReleaseDisplay);
63+
signal(ACQUIRE_DISPLAY_SIGNAL, onAcquireDisplay);
64+
65+
return ok;
66+
}
67+
68+
static void fixVtMode(int fd) {
69+
vt_mode getmodeReply = { 0 };
70+
int kernelDisplayMode = 0;
71+
bool modeFixed = false;
72+
bool ok = true;
73+
74+
if (ioctl(fd, VT_GETMODE, &getmodeReply) < 0) {
75+
qWarning() << "Failed to query VT mode:" << strerror(errno);
76+
ok = false;
77+
}
78+
79+
if (getmodeReply.mode != VT_AUTO)
80+
goto out;
81+
82+
if (ioctl(fd, KDGETMODE, &kernelDisplayMode) < 0) {
83+
qWarning() << "Failed to query kernel display mode:" << strerror(errno);
84+
ok = false;
85+
}
86+
87+
if (kernelDisplayMode == KD_TEXT)
88+
goto out;
89+
90+
// VT is in the VT_AUTO + KD_GRAPHICS state, fix it
91+
ok = handleVtSwitches(fd);
92+
modeFixed = true;
93+
out:
94+
if (!ok) {
95+
qCritical() << "Failed to set up VT mode";
96+
return;
97+
}
98+
99+
if (modeFixed)
100+
qDebug() << "VT mode fixed";
101+
else
102+
qDebug() << "VT mode didn't need to be fixed";
103+
}
104+
105+
int setUpNewVt() {
106+
// open VT master
107+
int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
108+
if (fd < 0) {
109+
qCritical() << "Failed to open VT master:" << strerror(errno);
110+
return -1;
111+
}
112+
113+
vt_stat vtState = { 0 };
114+
if (ioctl(fd, VT_GETSTATE, &vtState) < 0) {
115+
qCritical() << "Failed to get current VT:" << strerror(errno);
116+
close(fd);
117+
return -1;
118+
}
119+
120+
int vt = 0;
121+
if (ioctl(fd, VT_OPENQRY, &vt) < 0) {
122+
qCritical() << "Failed to open new VT:" << strerror(errno);
123+
close(fd);
124+
return -1;
125+
}
126+
127+
close(fd);
128+
129+
// fallback to active VT
130+
if (vt <= 0) {
131+
qWarning() << "New VT" << vt << "is not valid, fall back to" << vtState.v_active;
132+
return vtState.v_active;
133+
}
134+
135+
return vt;
136+
}
137+
138+
void jumpToVt(int vt) {
139+
qDebug() << "Jumping to VT" << vt;
140+
141+
int fd;
142+
143+
int activeVtFd = open("/dev/tty0", O_RDWR | O_NOCTTY);
144+
145+
QString ttyString = QString("/dev/tty%1").arg(vt);
146+
int vtFd = open(qPrintable(ttyString), O_RDWR | O_NOCTTY);
147+
if (vtFd != -1) {
148+
fd = vtFd;
149+
150+
// set graphics mode to prevent flickering
151+
if (ioctl(fd, KDSETMODE, KD_GRAPHICS) < 0)
152+
qWarning("Failed to set graphics mode for VT %d: %s", vt, strerror(errno));
153+
154+
// it's possible that the current VT was left in a broken
155+
// combination of states (KD_GRAPHICS with VT_AUTO) that we
156+
// cannot switch from, so make sure things are in a way that
157+
// will make VT_ACTIVATE work without hanging VT_WAITACTIVE
158+
fixVtMode(activeVtFd);
159+
} else {
160+
qWarning("Failed to open %s: %s", qPrintable(ttyString), strerror(errno));
161+
qDebug("Using /dev/tty0 instead of %s!", qPrintable(ttyString));
162+
fd = activeVtFd;
163+
}
164+
165+
handleVtSwitches(fd);
166+
167+
if (ioctl(fd, VT_ACTIVATE, vt) < 0)
168+
qWarning("Couldn't initiate jump to VT %d: %s", vt, strerror(errno));
169+
else if (ioctl(fd, VT_WAITACTIVE, vt) < 0)
170+
qWarning("Couldn't finalize jump to VT %d: %s", vt, strerror(errno));
171+
172+
close(activeVtFd);
173+
}
174+
}
175+
}

src/daemon/VirtualTerminal.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/***************************************************************************
2+
* Copyright (c) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the
16+
* Free Software Foundation, Inc.,
17+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
***************************************************************************/
19+
20+
#ifndef SDDM_VIRTUALTERMINAL_H
21+
#define SDDM_VIRTUALTERMINAL_H
22+
23+
namespace SDDM {
24+
namespace VirtualTerminal {
25+
int setUpNewVt();
26+
void jumpToVt(int vt);
27+
}
28+
}
29+
30+
#endif // SDDM_VIRTUALTERMINAL_H

src/helper/UserSession.cpp

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Session process wrapper
3+
* Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
34
* Copyright (C) 2014 Martin Bříza <mbriza@redhat.com>
45
*
56
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +24,7 @@
2324
#include "HelperApp.h"
2425

2526
#include <sys/types.h>
27+
#include <sys/ioctl.h>
2628
#include <unistd.h>
2729
#include <pwd.h>
2830
#include <grp.h>
@@ -69,6 +71,45 @@ namespace SDDM {
6971
}
7072

7173
void UserSession::setupChildProcess() {
74+
// Session type
75+
QString sessionType = processEnvironment().value("XDG_SESSION_TYPE");
76+
77+
// For Wayland sessions we leak the VT into the session as stdin so
78+
// that it stays open without races
79+
if (sessionType == QStringLiteral("wayland")) {
80+
// open VT and get the fd
81+
QString ttyString = QString("/dev/tty%1").arg(processEnvironment().value("XDG_VTNR"));
82+
int vtFd = ::open(qPrintable(ttyString), O_RDWR | O_NOCTTY);
83+
84+
// when this is true we'll take control of the tty
85+
bool takeControl = false;
86+
87+
if (vtFd > 0) {
88+
dup2(vtFd, STDIN_FILENO);
89+
::close(vtFd);
90+
takeControl = true;
91+
} else {
92+
int stdinFd = ::open("/dev/null", O_RDWR);
93+
dup2(stdinFd, STDIN_FILENO);
94+
::close(stdinFd);
95+
}
96+
97+
// set this process as session leader
98+
if (setsid() < 0) {
99+
qCritical("Failed to set pid %lld as leader of the new session and process group: %s",
100+
QCoreApplication::applicationPid(), strerror(errno));
101+
exit(Auth::HELPER_OTHER_ERROR);
102+
}
103+
104+
// take control of the tty
105+
if (takeControl) {
106+
if (ioctl(STDIN_FILENO, TIOCSCTTY) < 0) {
107+
qCritical("Failed to take control of the tty: %s", strerror(errno));
108+
exit(Auth::HELPER_OTHER_ERROR);
109+
}
110+
}
111+
}
112+
72113
const char *username = qobject_cast<HelperApp*>(parent())->user().toLocal8Bit();
73114
struct passwd *pw = getpwnam(username);
74115
if (setgid(pw->pw_gid) != 0) {
@@ -89,9 +130,6 @@ namespace SDDM {
89130
exit(Auth::HELPER_OTHER_ERROR);
90131
}
91132

92-
// Session type
93-
QString sessionType = processEnvironment().value("XDG_SESSION_TYPE");
94-
95133
//we cannot use setStandardError file as this code is run in the child process
96134
//we want to redirect after we setuid so that the log file is owned by the user
97135

src/helper/UserSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Session process wrapper
3+
* Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
34
* Copyright (C) 2014 Martin Bříza <mbriza@redhat.com>
45
*
56
* This program is free software; you can redistribute it and/or modify

0 commit comments

Comments
 (0)