feat: add KF6 Pty recipe source

This commit is contained in:
2026-05-07 08:02:04 +01:00
parent e67336a75f
commit 52954456f2
130 changed files with 845218 additions and 0 deletions
@@ -0,0 +1,106 @@
add_library(KF6Pty)
add_library(KF6::Pty ALIAS KF6Pty)
set_target_properties(KF6Pty PROPERTIES
VERSION ${KPTY_VERSION}
SOVERSION ${KPTY_SOVERSION}
EXPORT_NAME Pty
)
target_sources(KF6Pty PRIVATE
kpty.cpp
kptydevice.cpp
kptydevice.h
kpty.h
kpty_p.h
kptyprocess.cpp
kptyprocess.h
)
ecm_generate_export_header(KF6Pty
BASE_NAME KPty
GROUP_BASE_NAME KF
VERSION ${KPTY_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
)
ecm_qt_declare_logging_category(KF6Pty
HEADER kpty_debug.h
IDENTIFIER KPTY_LOG
CATEGORY_NAME kf.pty
DESCRIPTION "KPty"
EXPORT KPTY
)
include(ConfigureChecks.cmake)
configure_file(config-pty.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-pty.h )
target_include_directories(KF6Pty INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KPty>")
target_link_libraries(KF6Pty PUBLIC Qt6::Core
KF6::CoreAddons # KProcess
PRIVATE
${UTIL_LIBRARY}
KF6::I18n)
target_include_directories(KF6Pty PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
if(UTEMPTER_FOUND)
target_compile_definitions(KF6Pty PRIVATE ${UTEMPTER_COMPILE_FLAGS})
endif()
ecm_generate_headers(KPty_HEADERS
HEADER_NAMES
KPty
KPtyDevice
KPtyProcess
REQUIRED_HEADERS KPty_HEADERS
)
install(TARGETS KF6Pty EXPORT KF6PtyTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kpty_export.h
${KPty_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KPty COMPONENT Devel
)
if(BUILD_QCH)
ecm_add_qch(
KF6Pty_QCH
NAME KPty
BASE_NAME KF6Pty
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KPty_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
KF6CoreAddons_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KPTY_EXPORT
KPTY_DEPRECATED
KPTY_DEPRECATED_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
########### next target ###############
if (NOT HAVE_OPENPTY)
add_executable(kgrantpty kgrantpty.c)
ecm_mark_nongui_executable(kgrantpty)
install(TARGETS kgrantpty DESTINATION ${KDE_INSTALL_LIBEXECDIR_KF})
endif ()
ecm_qt_install_logging_categories(
EXPORT KPTY
FILE kpty.categories
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
@@ -0,0 +1,91 @@
include(CheckIncludeFile)
include(CheckIncludeFiles)
include(CheckSymbolExists)
include(CheckCXXSymbolExists)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(CheckStructHasMember)
check_include_files(sys/time.h HAVE_SYS_TIME_H)
if (UNIX)
check_include_files("sys/types.h;libutil.h" HAVE_LIBUTIL_H)
check_include_files(util.h HAVE_UTIL_H)
check_include_files(termios.h HAVE_TERMIOS_H)
check_include_files(termio.h HAVE_TERMIO_H)
check_include_files(pty.h HAVE_PTY_H)
check_include_files(sys/stropts.h HAVE_SYS_STROPTS_H)
check_include_files(sys/filio.h HAVE_SYS_FILIO_H)
set(UTIL_LIBRARY)
if (NOT UTEMPTER_FOUND)
check_function_exists(login login_in_libc)
if (NOT login_in_libc)
check_library_exists(util login "" login_in_libutil)
if (login_in_libutil)
set(UTIL_LIBRARY util)
endif (login_in_libutil)
endif (NOT login_in_libc)
if (CMAKE_SYSTEM_NAME MATCHES Linux OR CMAKE_SYSTEM_NAME MATCHES Darwin OR CMAKE_SYSTEM_NAME MATCHES kFreeBSD OR CMAKE_SYSTEM_NAME STREQUAL GNU)
set (HAVE_UTMPX)
else (CMAKE_SYSTEM_NAME MATCHES Linux OR CMAKE_SYSTEM_NAME MATCHES Darwin OR CMAKE_SYSTEM_NAME MATCHES kFreeBSD OR CMAKE_SYSTEM_NAME STREQUAL GNU)
check_function_exists(getutxent HAVE_UTMPX)
endif (CMAKE_SYSTEM_NAME MATCHES Linux OR CMAKE_SYSTEM_NAME MATCHES Darwin OR CMAKE_SYSTEM_NAME MATCHES kFreeBSD OR CMAKE_SYSTEM_NAME STREQUAL GNU)
if (HAVE_UTMPX)
set(utmp utmpx)
if (login_in_libutil)
check_library_exists(util loginx "" HAVE_LOGINX)
endif (login_in_libutil)
else (HAVE_UTMPX)
set(utmp utmp)
endif (HAVE_UTMPX)
if (login_in_libc OR login_in_libutil)
set(HAVE_LOGIN 1)
else (login_in_libc OR login_in_libutil)
set(HAVE_LOGIN)
check_struct_has_member("struct ${utmp}" "ut_type" "${utmp}.h" HAVE_STRUCT_UTMP_UT_TYPE)
check_struct_has_member("struct ${utmp}" "ut_pid" "${utmp}.h" HAVE_STRUCT_UTMP_UT_PID)
check_struct_has_member("struct ${utmp}" "ut_session" "${utmp}.h" HAVE_STRUCT_UTMP_UT_SESSION)
endif (login_in_libc OR login_in_libutil)
check_struct_has_member("struct ${utmp}" "ut_syslen" "${utmp}.h" HAVE_STRUCT_UTMP_UT_SYSLEN)
check_struct_has_member("struct ${utmp}" "ut_id" "${utmp}.h" HAVE_STRUCT_UTMP_UT_ID)
endif (NOT UTEMPTER_FOUND)
check_function_exists(openpty openpty_in_libc)
if (NOT openpty_in_libc)
check_library_exists(util openpty "" openpty_in_libutil)
if (openpty_in_libutil)
set(UTIL_LIBRARY util)
endif (openpty_in_libutil)
endif (NOT openpty_in_libc)
if (openpty_in_libc OR openpty_in_libutil)
set(HAVE_OPENPTY 1)
else (openpty_in_libc OR openpty_in_libutil)
set(HAVE_OPENPTY)
execute_process(
COMMAND sh -c "
for ptm in ptc ptmx ptm ptym/clone; do
if test -c /dev/$ptm; then
echo /dev/$ptm
break
fi
done"
OUTPUT_VARIABLE PTM_DEVICE
OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "PTY multiplexer: ${PTM_DEVICE}")
check_function_exists(revoke HAVE_REVOKE)
check_function_exists(_getpty HAVE__GETPTY)
check_function_exists(getpt HAVE_GETPT)
check_function_exists(grantpt HAVE_GRANTPT)
check_function_exists(unlockpt HAVE_UNLOCKPT)
check_function_exists(posix_openpt HAVE_POSIX_OPENPT)
endif (openpty_in_libc OR openpty_in_libutil)
check_function_exists(ptsname HAVE_PTSNAME)
check_function_exists(tcgetattr HAVE_TCGETATTR)
check_function_exists(tcsetattr HAVE_TCSETATTR)
endif (UNIX)
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources.
# The results are stored in a pseudo .cpp file to be picked up by xgettext.
lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg`
if [ -n "$lst" ] ; then
$EXTRACTRC $lst >> rc.cpp
fi
# Extract strings from all source files.
# If your framework depends on KI18n, use $XGETTEXT. If it uses Qt translation
# system, use $EXTRACT_TR_STRINGS.
$XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kpty6.pot
@@ -0,0 +1,47 @@
/* Defined to the path of the PTY multiplexer device, if any */
#cmakedefine PTM_DEVICE "${PTM_DEVICE}"
#cmakedefine01 HAVE_POSIX_OPENPT
#cmakedefine01 HAVE_GETPT
#cmakedefine01 HAVE_GRANTPT
#cmakedefine01 HAVE_OPENPTY
#cmakedefine01 HAVE_PTSNAME
#cmakedefine01 HAVE_REVOKE
#cmakedefine01 HAVE_UNLOCKPT
#cmakedefine01 HAVE__GETPTY
#cmakedefine01 HAVE_TCGETATTR
#cmakedefine01 HAVE_TCSETATTR
#cmakedefine01 HAVE_LIBUTIL_H
#cmakedefine01 HAVE_UTIL_H
#cmakedefine01 HAVE_PTY_H
/* Unused ? */
#cmakedefine01 HAVE_TERMIOS_H
#cmakedefine01 HAVE_TERMIO_H
#cmakedefine01 HAVE_SYS_STROPTS_H
#cmakedefine01 HAVE_SYS_FILIO_H
#cmakedefine01 HAVE_UTEMPTER
#cmakedefine01 HAVE_LOGIN
#cmakedefine01 HAVE_UTMPX
#cmakedefine01 HAVE_LOGINX
#cmakedefine01 HAVE_STRUCT_UTMP_UT_TYPE
#cmakedefine01 HAVE_STRUCT_UTMP_UT_PID
#cmakedefine01 HAVE_STRUCT_UTMP_UT_SESSION
#cmakedefine01 HAVE_STRUCT_UTMP_UT_SYSLEN
#cmakedefine01 HAVE_STRUCT_UTMP_UT_ID
#cmakedefine01 HAVE_SYS_TIME_H
/*
* Steven Schultz <sms at to.gd-es.com> tells us :
* BSD/OS 4.2 doesn't have a prototype for openpty in its system header files
*/
#ifdef __bsdi__
__BEGIN_DECLS
int openpty(int *, int *, char *, struct termios *, struct winsize *);
__END_DECLS
#endif
#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
#define KDE_INSTALL_LIBEXECDIR_KF "${KDE_INSTALL_LIBEXECDIR_KF}"
@@ -0,0 +1,186 @@
/* kgrantpty - helper program for KPty. */
/* This program is based on the glibc2.1 pt_chmod.
* It was pulled out from there since both Linux
* distributors and other OSes are not able to make
* use of the glibc for different reasons.
*
* THIS IS A ROOT SUID PROGRAM
*
* Things work as following:
*
* In konsole we open a master pty. This can be opened
* done by at most one process. Prior to opening the
* master pty, the slave pty cannot be opened. Then, in
* grantpty, we fork to this program. The trick is, that
* the parameter is passes as a file handle, which cannot
* be faked, so that we get a secure setuid root chmod/chown
* with this program.
*
* We have to chown/chmod the slave pty to prevent eavesdroping.
*
* SPDX-FileCopyrightText: 1998 Zack Weinberg <zack@rabi.phys.columbia.edu>
* SPDX-FileCopyrightText: 1999 Lars Doelle <lars.doelle@on-line.de>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-pty.h>
#include <cerrno>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#if HAVE_PTY_H
#include <pty.h>
#endif
#include <sys/param.h>
#if defined(__FreeBSD__)
#define BSD_PTY_HACK
#include <dirent.h>
#include <paths.h>
#endif
#define TTY_GROUP "tty"
int main(int argc, char *argv[])
{
struct stat st;
struct group *p;
gid_t gid;
uid_t uid;
mode_t mod;
char *tty;
int fd;
#if !HAVE_PTSNAME && defined(TIOCGPTN)
int ptyno;
char ttyb[32];
#endif
/* check preconditions **************************************************/
if (argc != 3 || (strcmp(argv[1], "--grant") && strcmp(argv[1], "--revoke"))) {
printf(
"usage: %s (--grant|--revoke) <file descriptor>\n"
"%s is a helper for the KDE core libraries.\n"
"It is not intended to be called from the command line.\n"
"It needs to be installed setuid root to function.\n",
argv[0],
argv[0]);
return 1; /* FAIL */
}
if (geteuid() != 0) {
fprintf(stderr, "%s not installed setuid root\n", argv[0]);
return 1; /* FAIL */
}
fd = atoi(argv[2]);
/* get slave pty name from master pty file handle *********/
#if HAVE_PTSNAME
tty = ptsname(fd);
if (!tty)
#elif defined(TIOCGPTN)
if (!ioctl(fd, TIOCGPTN, &ptyno)) {
sprintf(ttyb, "/dev/pts/%d", ptyno);
tty = ttyb;
} else
#endif
{
/* Check that fd is a valid master pseudo terminal. */
char *pty = ttyname(fd);
#ifdef BSD_PTY_HACK
if (pty == NULL) {
/*
Hack to make kgrantpty work on some versions of FreeBSD (and possibly
other systems): ttyname(3) does not work with a file descriptor opened
on a /dev/pty?? device.
Instead, this code looks through all the devices in /dev for a device
which has the same inode as our PTY_FILENO descriptor... if found, we
have the name for our pty.
*/
struct dirent *dirp;
DIR *dp;
struct stat dsb;
if (fstat(fd, &dsb) != -1) {
if ((dp = opendir(_PATH_DEV)) != NULL) {
while ((dirp = readdir(dp))) {
if (dirp->d_fileno != dsb.st_ino) {
continue;
}
pty = malloc(sizeof(_PATH_DEV) + strlen(dirp->d_name));
if (pty) {
strcpy(pty, _PATH_DEV);
strcat(pty, dirp->d_name);
}
break;
}
(void)closedir(dp);
}
}
}
#endif
if (pty == NULL) {
fprintf(stderr, "%s: cannot determine pty name.\n", argv[0]);
return 1; /* FAIL */
}
/* matches /dev/pty?? */
if (memcmp(pty, "/dev/pty", 8)) {
fprintf(stderr, "%s: determined a strange pty name '%s'.\n", argv[0], pty);
return 1; /* FAIL */
}
tty = malloc(strlen(pty) + 1);
strcpy(tty, "/dev/tty");
strcat(tty, pty + 8);
}
/* Check that the returned slave pseudo terminal is a character device. */
if (stat(tty, &st) < 0 || !S_ISCHR(st.st_mode)) {
fprintf(stderr, "%s: found '%s' not to be a character device.\n", argv[0], tty);
return 1; /* FAIL */
}
/* setup parameters for the operation ***********************************/
if (!strcmp(argv[1], "--grant")) {
uid = getuid();
p = getgrnam(TTY_GROUP);
if (!p) {
p = getgrnam("wheel");
}
gid = p ? p->gr_gid : getgid();
mod = S_IRUSR | S_IWUSR | S_IWGRP;
} else {
uid = 0;
gid = st.st_gid == getgid() ? 0 : -1;
mod = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
}
/* Perform the actual chown/chmod ************************************/
if (chown(tty, uid, gid) < 0) {
fprintf(stderr, "%s: cannot chown %s: %s\n", argv[0], tty, strerror(errno));
return 1; /* FAIL */
}
if (chmod(tty, mod) < 0) {
fprintf(stderr, "%s: cannot chmod %s: %s\n", argv[0], tty, strerror(errno));
return 1; /* FAIL */
}
return 0; /* OK */
}
@@ -0,0 +1,688 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2002-2003, 2007-2008 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2010 KDE e.V. <kde-ev-board@kde.org>
SPDX-FileContributor: 2010 Adriaan de Groot <groot@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpty_p.h"
#include <QProcess>
#include <kpty_debug.h>
// __USE_XOPEN isn't defined by default in ICC
// (needed for ptsname(), grantpt() and unlockpt())
#ifdef __INTEL_COMPILER
#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif
#endif
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <cerrno>
#include <fcntl.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#if HAVE_PTY_H
#include <pty.h>
#endif
#if HAVE_LIBUTIL_H
#include <libutil.h>
#elif HAVE_UTIL_H
#include <util.h>
#endif
#ifdef UTEMPTER_PATH
// utempter uses 'add' and 'del' whereas ulog-helper uses 'login' and 'logout'
#ifndef UTEMPTER_ULOG
#define UTEMPTER_ADD "add"
#define UTEMPTER_DEL "del"
#else
#define UTEMPTER_ADD "login"
#define UTEMPTER_DEL "logout"
#endif
class UtemptProcess : public QProcess
{
public:
UtemptProcess()
{
setChildProcessModifier([this]() {
// These are the file descriptors the utempter helper wants
dup2(cmdFd, 0);
dup2(cmdFd, 1);
dup2(cmdFd, 3);
});
}
int cmdFd;
};
#else
#include <utmp.h>
#if HAVE_UTMPX
#include <utmpx.h>
#endif
#if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE)
#define _PATH_UTMPX _UTMPX_FILE
#endif
#if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE)
#define _PATH_WTMPX _WTMPX_FILE
#endif
#endif
/* for HP-UX (some versions) the extern C is needed, and for other
platforms it doesn't hurt */
extern "C" {
#include <termios.h>
#if HAVE_TERMIO_H
#include <termio.h> // struct winsize on some systems
#endif
}
#if defined(_HPUX_SOURCE)
#define _TERMIOS_INCLUDED
#include <bsdtty.h>
#endif
#if HAVE_SYS_STROPTS_H
#include <sys/stropts.h> // Defines I_PUSH
#define _NEW_TTY_CTRL
#endif
#if HAVE_TCGETATTR
#define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode)
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__DragonFly__)
#define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode)
#else
#define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode)
#endif
#if HAVE_TCSETATTR
#define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode)
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__DragonFly__)
#define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode)
#else
#define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode)
#endif
#include <qplatformdefs.h>
#define TTY_GROUP "tty"
#ifndef PATH_MAX
#ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN
#else
#define PATH_MAX 1024
#endif
#endif
///////////////////////
// private functions //
///////////////////////
//////////////////
// private data //
//////////////////
KPtyPrivate::KPtyPrivate(KPty *parent)
: masterFd(-1)
, slaveFd(-1)
, ownMaster(true)
, q_ptr(parent)
{
#ifdef UTEMPTER_PATH
utempterPath = QStringLiteral(UTEMPTER_PATH);
#endif
}
KPtyPrivate::~KPtyPrivate()
{
}
#if !HAVE_OPENPTY
bool KPtyPrivate::chownpty(bool grant)
{
return !QProcess::execute(QFile::decodeName(CMAKE_INSTALL_PREFIX "/" KDE_INSTALL_LIBEXECDIR_KF "/kgrantpty"),
QStringList() << (grant ? "--grant" : "--revoke") << QString::number(masterFd));
}
#endif
/////////////////////////////
// public member functions //
/////////////////////////////
KPty::KPty()
: d_ptr(new KPtyPrivate(this))
{
}
KPty::KPty(KPtyPrivate *d)
: d_ptr(d)
{
d_ptr->q_ptr = this;
}
KPty::~KPty()
{
close();
}
bool KPty::open()
{
Q_D(KPty);
if (d->masterFd >= 0) {
return true;
}
d->ownMaster = true;
QByteArray ptyName;
// Find a master pty that we can open ////////////////////////////////
// Because not all the pty animals are created equal, they want to
// be opened by several different methods.
// We try, as we know them, one by one.
#if HAVE_OPENPTY
char ptsn[PATH_MAX];
if (::openpty(&d->masterFd, &d->slaveFd, ptsn, nullptr, nullptr)) {
d->masterFd = -1;
d->slaveFd = -1;
qCWarning(KPTY_LOG) << "Can't open a pseudo teletype";
return false;
}
d->ttyName = ptsn;
#else
#if HAVE_PTSNAME || defined(TIOCGPTN)
#if HAVE_POSIX_OPENPT
d->masterFd = ::posix_openpt(O_RDWR | O_NOCTTY);
#elif HAVE_GETPT
d->masterFd = ::getpt();
#elif defined(PTM_DEVICE)
d->masterFd = QT_OPEN(PTM_DEVICE, QT_OPEN_RDWR | O_NOCTTY);
#else
#error No method to open a PTY master detected.
#endif
if (d->masterFd >= 0) {
#if HAVE_PTSNAME
char *ptsn = ptsname(d->masterFd);
if (ptsn) {
d->ttyName = ptsn;
#else
int ptyno;
if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) {
char buf[32];
sprintf(buf, "/dev/pts/%d", ptyno);
d->ttyName = buf;
#endif
#if HAVE_GRANTPT
if (!grantpt(d->masterFd)) {
goto grantedpt;
}
#else
goto gotpty;
#endif
}
::close(d->masterFd);
d->masterFd = -1;
}
#endif // HAVE_PTSNAME || TIOCGPTN
// Linux device names, FIXME: Trouble on other systems?
for (const char *s3 = "pqrstuvwxyzabcde"; *s3; s3++) {
for (const char *s4 = "0123456789abcdef"; *s4; s4++) {
ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toLatin1();
d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toLatin1();
d->masterFd = QT_OPEN(ptyName.data(), QT_OPEN_RDWR);
if (d->masterFd >= 0) {
if (!access(d->ttyName.data(), R_OK | W_OK)) { // checks availability based on permission bits
if (!geteuid()) {
struct group *p = getgrnam(TTY_GROUP);
if (!p) {
p = getgrnam("wheel");
}
gid_t gid = p ? p->gr_gid : getgid();
chown(d->ttyName.data(), getuid(), gid);
chmod(d->ttyName.data(), S_IRUSR | S_IWUSR | S_IWGRP);
}
goto gotpty;
}
::close(d->masterFd);
d->masterFd = -1;
}
}
}
qCWarning(KPTY_LOG) << "Can't open a pseudo teletype";
return false;
gotpty:
QFileInfo info(d->ttyName.data());
if (!info.exists()) {
return false; // this just cannot happen ... *cough* Yeah right, I just
}
// had it happen when pty #349 was allocated. I guess
// there was some sort of leak? I only had a few open.
constexpr auto perms = QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther;
if ((info.ownerId() != getuid() || (info.permissions() & perms)) //
&& !d->chownpty(true)) {
qCWarning(KPTY_LOG) << "chownpty failed for device " << ptyName << "::" << d->ttyName << "\nThis means the communication can be eavesdropped." << endl;
}
grantedpt:
#ifdef HAVE_REVOKE
revoke(d->ttyName.data());
#endif
#ifdef HAVE_UNLOCKPT
unlockpt(d->masterFd);
#elif defined(TIOCSPTLCK)
int flag = 0;
ioctl(d->masterFd, TIOCSPTLCK, &flag);
#endif
d->slaveFd = QT_OPEN(d->ttyName.data(), QT_OPEN_RDWR | O_NOCTTY);
if (d->slaveFd < 0) {
qCWarning(KPTY_LOG) << "Can't open slave pseudo teletype";
::close(d->masterFd);
d->masterFd = -1;
return false;
}
#endif /* HAVE_OPENPTY */
fcntl(d->masterFd, F_SETFD, FD_CLOEXEC);
fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC);
return true;
}
bool KPty::open(int fd)
{
#if !HAVE_PTSNAME && !defined(TIOCGPTN)
qCWarning(KPTY_LOG) << "Unsupported attempt to open pty with fd" << fd;
return false;
#else
Q_D(KPty);
if (d->masterFd >= 0) {
qCWarning(KPTY_LOG) << "Attempting to open an already open pty";
return false;
}
d->ownMaster = false;
#if HAVE_PTSNAME
char *ptsn = ptsname(fd);
if (ptsn) {
d->ttyName = ptsn;
#else
int ptyno;
if (!ioctl(fd, TIOCGPTN, &ptyno)) {
char buf[32];
sprintf(buf, "/dev/pts/%d", ptyno);
d->ttyName = buf;
#endif
} else {
qCWarning(KPTY_LOG) << "Failed to determine pty slave device for fd" << fd;
return false;
}
d->masterFd = fd;
if (!openSlave()) {
d->masterFd = -1;
return false;
}
return true;
#endif
}
void KPty::closeSlave()
{
Q_D(KPty);
if (d->slaveFd < 0) {
return;
}
::close(d->slaveFd);
d->slaveFd = -1;
}
bool KPty::openSlave()
{
Q_D(KPty);
if (d->slaveFd >= 0) {
return true;
}
if (d->masterFd < 0) {
qCWarning(KPTY_LOG) << "Attempting to open pty slave while master is closed";
return false;
}
d->slaveFd = QT_OPEN(d->ttyName.data(), QT_OPEN_RDWR | O_NOCTTY);
if (d->slaveFd < 0) {
qCWarning(KPTY_LOG) << "Can't open slave pseudo teletype";
return false;
}
fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC);
return true;
}
void KPty::close()
{
Q_D(KPty);
if (d->masterFd < 0) {
return;
}
closeSlave();
if (d->ownMaster) {
#if !HAVE_OPENPTY
// don't bother resetting unix98 pty, it will go away after closing master anyway.
if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) {
if (!geteuid()) {
struct stat st;
if (!stat(d->ttyName.data(), &st)) {
chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1);
chmod(d->ttyName.data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
}
} else {
fcntl(d->masterFd, F_SETFD, 0);
d->chownpty(false);
}
}
#endif
::close(d->masterFd);
}
d->masterFd = -1;
}
void KPty::setCTty()
{
Q_D(KPty);
if (!d->withCTty) {
return;
}
// Setup job control //////////////////////////////////
// Become session leader, process group leader,
// and get rid of the old controlling terminal.
setsid();
// make our slave pty the new controlling terminal.
ioctl(d->slaveFd, TIOCSCTTY, 0);
// make our new process group the foreground group on the pty
int pgrp = getpid();
tcsetpgrp(d->slaveFd, pgrp);
}
void KPty::login(const char *user, const char *remotehost)
{
#ifdef UTEMPTER_PATH
Q_D(KPty);
Q_UNUSED(user);
// Emulating libutempter version 1.1.6
if (!d->utempterPath.isEmpty()) {
UtemptProcess utemptProcess;
utemptProcess.cmdFd = d->masterFd;
utemptProcess.setProgram(d->utempterPath);
utemptProcess.setArguments(QStringList() << QStringLiteral(UTEMPTER_ADD) << QString::fromLocal8Bit(remotehost));
utemptProcess.setProcessChannelMode(QProcess::ForwardedChannels);
utemptProcess.start();
utemptProcess.waitForFinished();
}
#else
#if HAVE_UTMPX
struct utmpx l_struct;
#else
struct utmp l_struct;
#endif
memset(&l_struct, 0, sizeof(l_struct));
// note: strncpy without terminators _is_ correct here. man 4 utmp
if (user) {
strncpy(l_struct.ut_name, user, sizeof(l_struct.ut_name));
}
if (remotehost) {
strncpy(l_struct.ut_host, remotehost, sizeof(l_struct.ut_host));
#if HAVE_STRUCT_UTMP_UT_SYSLEN
l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host));
#endif
}
#ifndef __GLIBC__
Q_D(KPty);
const char *str_ptr = d->ttyName.data();
if (!memcmp(str_ptr, "/dev/", 5)) {
str_ptr += 5;
}
strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
#if HAVE_STRUCT_UTMP_UT_ID
strncpy(l_struct.ut_id, str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id), sizeof(l_struct.ut_id));
#endif
#endif
#if HAVE_UTMPX
gettimeofday(&l_struct.ut_tv, 0);
#else
l_struct.ut_time = time(0);
#endif
#if HAVE_LOGIN
#if HAVE_LOGINX
::loginx(&l_struct);
#else
::login(&l_struct);
#endif
#else
#if HAVE_STRUCT_UTMP_UT_TYPE
l_struct.ut_type = USER_PROCESS;
#endif
#if HAVE_STRUCT_UTMP_UT_PID
l_struct.ut_pid = getpid();
#if HAVE_STRUCT_UTMP_UT_SESSION
l_struct.ut_session = getsid(0);
#endif
#endif
#if HAVE_UTMPX
utmpxname(_PATH_UTMPX);
setutxent();
pututxline(&l_struct);
endutxent();
updwtmpx(_PATH_WTMPX, &l_struct);
#else
utmpname(_PATH_UTMP);
setutent();
pututline(&l_struct);
endutent();
updwtmp(_PATH_WTMP, &l_struct);
#endif
#endif
#endif
}
void KPty::logout()
{
#ifdef UTEMPTER_PATH
Q_D(KPty);
// Emulating libutempter version 1.1.6
if (!d->utempterPath.isEmpty()) {
UtemptProcess utemptProcess;
utemptProcess.cmdFd = d->masterFd;
utemptProcess.setProgram(d->utempterPath);
utemptProcess.setArguments(QStringList(QStringLiteral(UTEMPTER_DEL)));
utemptProcess.setProcessChannelMode(QProcess::ForwardedChannels);
utemptProcess.start();
utemptProcess.waitForFinished();
}
#else
Q_D(KPty);
const char *str_ptr = d->ttyName.data();
if (!memcmp(str_ptr, "/dev/", 5)) {
str_ptr += 5;
}
#ifdef __GLIBC__
else {
const char *sl_ptr = strrchr(str_ptr, '/');
if (sl_ptr) {
str_ptr = sl_ptr + 1;
}
}
#endif
#if HAVE_LOGIN
#if HAVE_LOGINX
::logoutx(str_ptr, 0, DEAD_PROCESS);
#else
::logout(str_ptr);
#endif
#else
#if HAVE_UTMPX
struct utmpx l_struct, *ut;
#else
struct utmp l_struct, *ut;
#endif
memset(&l_struct, 0, sizeof(l_struct));
strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
#if HAVE_UTMPX
utmpxname(_PATH_UTMPX);
setutxent();
if ((ut = getutxline(&l_struct))) {
#else
utmpname(_PATH_UTMP);
setutent();
if ((ut = getutline(&l_struct))) {
#endif
memset(ut->ut_name, 0, sizeof(*ut->ut_name));
memset(ut->ut_host, 0, sizeof(*ut->ut_host));
#if HAVE_STRUCT_UTMP_UT_SYSLEN
ut->ut_syslen = 0;
#endif
#if HAVE_STRUCT_UTMP_UT_TYPE
ut->ut_type = DEAD_PROCESS;
#endif
#if HAVE_UTMPX
gettimeofday(&(ut->ut_tv), 0);
pututxline(ut);
}
endutxent();
#else
ut->ut_time = time(0);
pututline(ut);
}
endutent();
#endif
#endif
#endif
}
bool KPty::tcGetAttr(struct ::termios *ttmode) const
{
Q_D(const KPty);
return _tcgetattr(d->masterFd, ttmode) == 0;
}
bool KPty::tcSetAttr(struct ::termios *ttmode)
{
Q_D(KPty);
return _tcsetattr(d->masterFd, ttmode) == 0;
}
bool KPty::setWinSize(int lines, int columns, int height, int width)
{
Q_D(KPty);
struct winsize winSize;
winSize.ws_row = (unsigned short)lines;
winSize.ws_col = (unsigned short)columns;
winSize.ws_ypixel = (unsigned short)height;
winSize.ws_xpixel = (unsigned short)width;
return ioctl(d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0;
}
bool KPty::setWinSize(int lines, int columns)
{
return setWinSize(lines, columns, 0, 0);
}
bool KPty::setEcho(bool echo)
{
struct ::termios ttmode;
if (!tcGetAttr(&ttmode)) {
return false;
}
if (!echo) {
ttmode.c_lflag &= ~ECHO;
} else {
ttmode.c_lflag |= ECHO;
}
return tcSetAttr(&ttmode);
}
const char *KPty::ttyName() const
{
Q_D(const KPty);
return d->ttyName.data();
}
int KPty::masterFd() const
{
Q_D(const KPty);
return d->masterFd;
}
int KPty::slaveFd() const
{
Q_D(const KPty);
return d->slaveFd;
}
void KPty::setCTtyEnabled(bool enable)
{
Q_D(KPty);
d->withCTty = enable;
}
+223
View File
@@ -0,0 +1,223 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef kpty_h
#define kpty_h
#include "kpty_export.h"
#include <qglobal.h>
#include <memory>
class KPtyPrivate;
struct termios;
/**
* Provides primitives for opening & closing a pseudo TTY pair, assigning the
* controlling TTY, utmp registration and setting various terminal attributes.
*/
class KPTY_EXPORT KPty
{
Q_DECLARE_PRIVATE(KPty)
public:
/**
* Constructor
*/
KPty();
/**
* Destructor:
*
* If the pty is still open, it will be closed. Note, however, that
* an utmp registration is @em not undone.
*/
~KPty();
KPty(const KPty &) = delete;
KPty &operator=(const KPty &) = delete;
/**
* Create a pty master/slave pair.
*
* @return true if a pty pair was successfully opened
*/
bool open();
/**
* Open using an existing pty master.
*
* @param fd an open pty master file descriptor.
* The ownership of the fd remains with the caller;
* it will not be automatically closed at any point.
* @return true if a pty pair was successfully opened
*/
bool open(int fd);
/**
* Close the pty master/slave pair.
*/
void close();
/**
* Close the pty slave descriptor.
*
* When creating the pty, KPty also opens the slave and keeps it open.
* Consequently the master will never receive an EOF notification.
* Usually this is the desired behavior, as a closed pty slave can be
* reopened any time - unlike a pipe or socket. However, in some cases
* pipe-alike behavior might be desired.
*
* After this function was called, slaveFd() and setCTty() cannot be
* used.
*/
void closeSlave();
/**
* Open the pty slave descriptor.
*
* This undoes the effect of closeSlave().
*
* @return true if the pty slave was successfully opened
*/
bool openSlave();
/**
* @brief Whether this will be a controlling terminal
*
* This is on by default.
* Disabling the controllig aspect only makes sense if another process will
* take over control or there is nothing to control or for technical reasons
* control cannot be set (this notably is the case with flatpak-spawn when
* used inside a sandbox).
*
* @param enable whether to enable ctty set up
*/
void setCTtyEnabled(bool enable);
/**
* Creates a new session and process group and makes this pty the
* controlling tty.
*/
void setCTty();
/**
* Creates an utmp entry for the tty.
* This function must be called after calling setCTty and
* making this pty the stdin.
* @param user the user to be logged on
* @param remotehost the host from which the login is coming. This is
* @em not the local host. For remote logins it should be the hostname
* of the client. For local logins from inside an X session it should
* be the name of the X display. Otherwise it should be empty.
*/
void login(const char *user = nullptr, const char *remotehost = nullptr);
/**
* Removes the utmp entry for this tty.
*/
void logout();
/**
* Wrapper around tcgetattr(3).
*
* This function can be used only while the PTY is open.
* You will need an #include &lt;termios.h&gt; to do anything useful
* with it.
*
* @param ttmode a pointer to a termios structure.
* Note: when declaring ttmode, @c struct @c ::termios must be used -
* without the '::' some version of HP-UX thinks, this declares
* the struct in your class, in your method.
* @return @c true on success, false otherwise
*/
bool tcGetAttr(struct ::termios *ttmode) const;
/**
* Wrapper around tcsetattr(3) with mode TCSANOW.
*
* This function can be used only while the PTY is open.
*
* @param ttmode a pointer to a termios structure.
* @return @c true on success, false otherwise. Note that success means
* that @em at @em least @em one attribute could be set.
*/
bool tcSetAttr(struct ::termios *ttmode);
/**
* Change the logical (screen) size of the pty.
* The default is 24 lines by 80 columns in characters, and zero pixels.
*
* This function can be used only while the PTY is open.
*
* @param lines the number of character rows
* @param columns the number of character columns
* @param height the view height in pixels
* @param width the view width in pixels
* @return @c true on success, false otherwise
*
* @since 5.93
*/
bool setWinSize(int lines, int columns, int height, int width);
/**
* @overload
* Change the logical (screen) size of the pty.
* The pixel size is set to zero.
*/
bool setWinSize(int lines, int columns);
/**
* Set whether the pty should echo input.
*
* Echo is on by default.
* If the output of automatically fed (non-interactive) PTY clients
* needs to be parsed, disabling echo often makes it much simpler.
*
* This function can be used only while the PTY is open.
*
* @param echo true if input should be echoed.
* @return @c true on success, false otherwise
*/
bool setEcho(bool echo);
/**
* @return the name of the slave pty device.
*
* This function should be called only while the pty is open.
*/
const char *ttyName() const;
/**
* @return the file descriptor of the master pty
*
* This function should be called only while the pty is open.
*/
int masterFd() const;
/**
* @return the file descriptor of the slave pty
*
* This function should be called only while the pty slave is open.
*/
int slaveFd() const;
protected:
/**
* @internal
*/
KPTY_NO_EXPORT explicit KPty(KPtyPrivate *d);
/**
* @internal
*/
std::unique_ptr<KPtyPrivate> const d_ptr;
};
#endif
@@ -0,0 +1,42 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef kpty_p_h
#define kpty_p_h
#include "kpty.h"
#include <config-pty.h>
#include <QByteArray>
#include <QString>
class KPtyPrivate
{
public:
Q_DECLARE_PUBLIC(KPty)
KPtyPrivate(KPty *parent);
virtual ~KPtyPrivate();
#if !HAVE_OPENPTY
bool chownpty(bool grant);
#endif
int masterFd;
int slaveFd;
bool ownMaster : 1;
QByteArray ttyName;
QString utempterPath;
bool withCTty = true;
KPty *q_ptr;
};
#endif
@@ -0,0 +1,585 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2010 KDE e.V. <kde-ev-board@kde.org>
SPDX-FileContributor: 2010 Adriaan de Groot <groot@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kptydevice.h"
#include "kpty_p.h"
#include <config-pty.h>
#include <QSocketNotifier>
#include <KLocalizedString>
#include <cerrno>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#if HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
// "the other end's output queue size" -- that is is our end's input
#define PTY_BYTES_AVAILABLE TIOCOUTQ
#elif defined(TIOCINQ)
// "our end's input queue size"
#define PTY_BYTES_AVAILABLE TIOCINQ
#else
// likewise. more generic ioctl (theoretically)
#define PTY_BYTES_AVAILABLE FIONREAD
#endif
#define KMAXINT ((int)(~0U >> 1))
/////////////////////////////////////////////////////
// Helper. Remove when QRingBuffer becomes public. //
/////////////////////////////////////////////////////
#include <QByteArray>
#include <QList>
#define CHUNKSIZE 4096
class KRingBuffer
{
public:
KRingBuffer()
{
clear();
}
void clear()
{
buffers.clear();
QByteArray tmp;
tmp.resize(CHUNKSIZE);
buffers << tmp;
head = tail = 0;
totalSize = 0;
}
inline bool isEmpty() const
{
return buffers.count() == 1 && !tail;
}
inline int size() const
{
return totalSize;
}
inline int readSize() const
{
return (buffers.count() == 1 ? tail : buffers.first().size()) - head;
}
inline const char *readPointer() const
{
Q_ASSERT(totalSize > 0);
return buffers.first().constData() + head;
}
void free(int bytes)
{
totalSize -= bytes;
Q_ASSERT(totalSize >= 0);
for (;;) {
int nbs = readSize();
if (bytes < nbs) {
head += bytes;
if (head == tail && buffers.count() == 1) {
buffers.first().resize(CHUNKSIZE);
head = tail = 0;
}
break;
}
bytes -= nbs;
if (buffers.count() == 1) {
buffers.first().resize(CHUNKSIZE);
head = tail = 0;
break;
}
buffers.removeFirst();
head = 0;
}
}
char *reserve(int bytes)
{
totalSize += bytes;
char *ptr;
if (tail + bytes <= buffers.last().size()) {
ptr = buffers.last().data() + tail;
tail += bytes;
} else {
buffers.last().resize(tail);
QByteArray tmp;
tmp.resize(qMax(CHUNKSIZE, bytes));
ptr = tmp.data();
buffers << tmp;
tail = bytes;
}
return ptr;
}
// release a trailing part of the last reservation
inline void unreserve(int bytes)
{
totalSize -= bytes;
tail -= bytes;
}
inline void write(const char *data, int len)
{
memcpy(reserve(len), data, len);
}
// Find the first occurrence of c and return the index after it.
// If c is not found until maxLength, maxLength is returned, provided
// it is smaller than the buffer size. Otherwise -1 is returned.
int indexAfter(char c, int maxLength = KMAXINT) const
{
int index = 0;
int start = head;
QList<QByteArray>::ConstIterator it = buffers.begin();
for (;;) {
if (!maxLength) {
return index;
}
if (index == size()) {
return -1;
}
const QByteArray &buf = *it;
++it;
int len = qMin((it == buffers.end() ? tail : buf.size()) - start, maxLength);
const char *ptr = buf.data() + start;
if (const char *rptr = (const char *)memchr(ptr, c, len)) {
return index + (rptr - ptr) + 1;
}
index += len;
maxLength -= len;
start = 0;
}
}
inline int lineSize(int maxLength = KMAXINT) const
{
return indexAfter('\n', maxLength);
}
inline bool canReadLine() const
{
return lineSize() != -1;
}
int read(char *data, int maxLength)
{
int bytesToRead = qMin(size(), maxLength);
int readSoFar = 0;
while (readSoFar < bytesToRead) {
const char *ptr = readPointer();
int bs = qMin(bytesToRead - readSoFar, readSize());
memcpy(data + readSoFar, ptr, bs);
readSoFar += bs;
free(bs);
}
return readSoFar;
}
int readLine(char *data, int maxLength)
{
return read(data, lineSize(qMin(maxLength, size())));
}
private:
QList<QByteArray> buffers;
int head, tail;
int totalSize;
};
//////////////////
// private data //
//////////////////
// Lifted from Qt. I don't think they would mind. ;)
// Re-lift again from Qt whenever a proper replacement for pthread_once appears
static void qt_ignore_sigpipe()
{
static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0);
if (atom.testAndSetRelaxed(0, 1)) {
struct sigaction noaction;
memset(&noaction, 0, sizeof(noaction));
noaction.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &noaction, nullptr);
}
}
/* clang-format off */
#define NO_INTR(ret, func) \
do { \
ret = func; \
} while (ret < 0 && errno == EINTR)
/* clang-format on */
class KPtyDevicePrivate : public KPtyPrivate
{
Q_DECLARE_PUBLIC(KPtyDevice)
public:
KPtyDevicePrivate(KPty *parent)
: KPtyPrivate(parent)
, emittedReadyRead(false)
, emittedBytesWritten(false)
, readNotifier(nullptr)
, writeNotifier(nullptr)
{
}
bool _k_canRead();
bool _k_canWrite();
bool doWait(int msecs, bool reading);
void finishOpen(QIODevice::OpenMode mode);
bool emittedReadyRead;
bool emittedBytesWritten;
QSocketNotifier *readNotifier;
QSocketNotifier *writeNotifier;
KRingBuffer readBuffer;
KRingBuffer writeBuffer;
};
bool KPtyDevicePrivate::_k_canRead()
{
Q_Q(KPtyDevice);
qint64 readBytes = 0;
int available;
if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *)&available)) {
char *ptr = readBuffer.reserve(available);
NO_INTR(readBytes, read(q->masterFd(), ptr, available));
if (readBytes < 0) {
readBuffer.unreserve(available);
q->setErrorString(i18n("Error reading from PTY"));
return false;
}
readBuffer.unreserve(available - readBytes); // *should* be a no-op
}
if (!readBytes) {
readNotifier->setEnabled(false);
Q_EMIT q->readEof();
return false;
} else {
if (!emittedReadyRead) {
emittedReadyRead = true;
Q_EMIT q->readyRead();
emittedReadyRead = false;
}
return true;
}
}
bool KPtyDevicePrivate::_k_canWrite()
{
Q_Q(KPtyDevice);
writeNotifier->setEnabled(false);
if (writeBuffer.isEmpty()) {
return false;
}
qt_ignore_sigpipe();
int wroteBytes;
NO_INTR(wroteBytes, write(q->masterFd(), writeBuffer.readPointer(), writeBuffer.readSize()));
if (wroteBytes < 0) {
q->setErrorString(i18n("Error writing to PTY"));
return false;
}
writeBuffer.free(wroteBytes);
if (!emittedBytesWritten) {
emittedBytesWritten = true;
Q_EMIT q->bytesWritten(wroteBytes);
emittedBytesWritten = false;
}
if (!writeBuffer.isEmpty()) {
writeNotifier->setEnabled(true);
}
return true;
}
#ifndef timeradd
// Lifted from GLIBC
/* clang-format off */
#define timeradd(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
if ((result)->tv_usec >= 1000000) { \
++(result)->tv_sec; \
(result)->tv_usec -= 1000000; \
} \
} while (0)
#define timersub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) { \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
#endif
/* clang-format on */
bool KPtyDevicePrivate::doWait(int msecs, bool reading)
{
Q_Q(KPtyDevice);
#ifndef Q_OS_LINUX
struct timeval etv;
#endif
struct timeval tv;
struct timeval *tvp;
if (msecs < 0) {
tvp = nullptr;
} else {
tv.tv_sec = msecs / 1000;
tv.tv_usec = (msecs % 1000) * 1000;
#ifndef Q_OS_LINUX
gettimeofday(&etv, nullptr);
timeradd(&tv, &etv, &etv);
#endif
tvp = &tv;
}
while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) {
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if (readNotifier->isEnabled()) {
FD_SET(q->masterFd(), &rfds);
}
if (!writeBuffer.isEmpty()) {
FD_SET(q->masterFd(), &wfds);
}
#ifndef Q_OS_LINUX
if (tvp) {
gettimeofday(&tv, nullptr);
timersub(&etv, &tv, &tv);
if (tv.tv_sec < 0) {
tv.tv_sec = tv.tv_usec = 0;
}
}
#endif
switch (select(q->masterFd() + 1, &rfds, &wfds, nullptr, tvp)) {
case -1:
if (errno == EINTR) {
break;
}
return false;
case 0:
q->setErrorString(i18n("PTY operation timed out"));
return false;
default:
if (FD_ISSET(q->masterFd(), &rfds)) {
bool canRead = _k_canRead();
if (reading && canRead) {
return true;
}
}
if (FD_ISSET(q->masterFd(), &wfds)) {
bool canWrite = _k_canWrite();
if (!reading) {
return canWrite;
}
}
break;
}
}
return false;
}
void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode)
{
Q_Q(KPtyDevice);
q->QIODevice::open(mode);
fcntl(q->masterFd(), F_SETFL, O_NONBLOCK);
readBuffer.clear();
readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q);
writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q);
QObject::connect(readNotifier, &QSocketNotifier::activated, q, [this]() {
_k_canRead();
});
QObject::connect(writeNotifier, &QSocketNotifier::activated, q, [this]() {
_k_canWrite();
});
readNotifier->setEnabled(true);
}
/////////////////////////////
// public member functions //
/////////////////////////////
KPtyDevice::KPtyDevice(QObject *parent)
: QIODevice(parent)
, KPty(new KPtyDevicePrivate(this))
{
}
KPtyDevice::~KPtyDevice()
{
close();
}
bool KPtyDevice::open(OpenMode mode)
{
Q_D(KPtyDevice);
if (masterFd() >= 0) {
return true;
}
if (!KPty::open()) {
setErrorString(i18n("Error opening PTY"));
return false;
}
d->finishOpen(mode);
return true;
}
bool KPtyDevice::open(int fd, OpenMode mode)
{
Q_D(KPtyDevice);
if (!KPty::open(fd)) {
setErrorString(i18n("Error opening PTY"));
return false;
}
d->finishOpen(mode);
return true;
}
void KPtyDevice::close()
{
Q_D(KPtyDevice);
if (masterFd() < 0) {
return;
}
delete d->readNotifier;
delete d->writeNotifier;
QIODevice::close();
KPty::close();
}
bool KPtyDevice::isSequential() const
{
return true;
}
bool KPtyDevice::canReadLine() const
{
Q_D(const KPtyDevice);
return QIODevice::canReadLine() || d->readBuffer.canReadLine();
}
bool KPtyDevice::atEnd() const
{
Q_D(const KPtyDevice);
return QIODevice::atEnd() && d->readBuffer.isEmpty();
}
qint64 KPtyDevice::bytesAvailable() const
{
Q_D(const KPtyDevice);
return QIODevice::bytesAvailable() + d->readBuffer.size();
}
qint64 KPtyDevice::bytesToWrite() const
{
Q_D(const KPtyDevice);
return d->writeBuffer.size();
}
bool KPtyDevice::waitForReadyRead(int msecs)
{
Q_D(KPtyDevice);
return d->doWait(msecs, true);
}
bool KPtyDevice::waitForBytesWritten(int msecs)
{
Q_D(KPtyDevice);
return d->doWait(msecs, false);
}
void KPtyDevice::setSuspended(bool suspended)
{
Q_D(KPtyDevice);
d->readNotifier->setEnabled(!suspended);
}
bool KPtyDevice::isSuspended() const
{
Q_D(const KPtyDevice);
return !d->readNotifier->isEnabled();
}
// protected
qint64 KPtyDevice::readData(char *data, qint64 maxlen)
{
Q_D(KPtyDevice);
return d->readBuffer.read(data, (int)qMin<qint64>(maxlen, KMAXINT));
}
// protected
qint64 KPtyDevice::readLineData(char *data, qint64 maxlen)
{
Q_D(KPtyDevice);
return d->readBuffer.readLine(data, (int)qMin<qint64>(maxlen, KMAXINT));
}
// protected
qint64 KPtyDevice::writeData(const char *data, qint64 len)
{
Q_D(KPtyDevice);
Q_ASSERT(len <= KMAXINT);
d->writeBuffer.write(data, len);
d->writeNotifier->setEnabled(true);
return len;
}
#include "moc_kptydevice.cpp"
@@ -0,0 +1,132 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef kptydev_h
#define kptydev_h
#include "kpty.h"
#include <QIODevice>
class KPtyDevicePrivate;
/**
* Encapsulates KPty into a QIODevice, so it can be used with Q*Stream, etc.
*/
class KPTY_EXPORT KPtyDevice : public QIODevice, public KPty // krazy:exclude=dpointer (via macro)
{
Q_OBJECT
Q_DECLARE_PRIVATE_D(KPty::d_ptr, KPtyDevice)
public:
/**
* Constructor
*/
explicit KPtyDevice(QObject *parent = nullptr);
/**
* Destructor:
*
* If the pty is still open, it will be closed. Note, however, that
* an utmp registration is @em not undone.
*/
~KPtyDevice() override;
/**
* Create a pty master/slave pair.
*
* @return true if a pty pair was successfully opened
*/
bool open(OpenMode mode = ReadWrite | Unbuffered) override;
/**
* Open using an existing pty master. The ownership of the fd
* remains with the caller, i.e., close() will not close the fd.
*
* This is useful if you wish to attach a secondary "controller" to an
* existing pty device such as a terminal widget.
* Note that you will need to use setSuspended() on both devices to
* control which one gets the incoming data from the pty.
*
* @param fd an open pty master file descriptor.
* @param mode the device mode to open the pty with.
* @return true if a pty pair was successfully opened
*/
bool open(int fd, OpenMode mode = ReadWrite | Unbuffered);
/**
* Close the pty master/slave pair.
*/
void close() override;
/**
* Sets whether the KPtyDevice monitors the pty for incoming data.
*
* When the KPtyDevice is suspended, it will no longer attempt to buffer
* data that becomes available from the pty and it will not emit any
* signals.
*
* Do not use on closed ptys.
* After a call to open(), the pty is not suspended. If you need to
* ensure that no data is read, call this function before the main loop
* is entered again (i.e., immediately after opening the pty).
*/
void setSuspended(bool suspended);
/**
* Returns true if the KPtyDevice is not monitoring the pty for incoming
* data.
*
* Do not use on closed ptys.
*
* See setSuspended()
*/
bool isSuspended() const;
/**
* @return always true
*/
bool isSequential() const override;
/**
* @reimp
*/
bool canReadLine() const override;
/**
* @reimp
*/
bool atEnd() const override;
/**
* @reimp
*/
qint64 bytesAvailable() const override;
/**
* @reimp
*/
qint64 bytesToWrite() const override;
bool waitForBytesWritten(int msecs = -1) override;
bool waitForReadyRead(int msecs = -1) override;
Q_SIGNALS:
/**
* Emitted when EOF is read from the PTY.
*
* Data may still remain in the buffers.
*/
void readEof();
protected:
qint64 readData(char *data, qint64 maxSize) override;
qint64 readLineData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 maxSize) override;
};
#endif
@@ -0,0 +1,124 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kptyprocess.h"
#include <kptydevice.h>
#include <kuser.h>
#include <stdlib.h>
#include <unistd.h>
//////////////////
// private data //
//////////////////
class KPtyProcessPrivate
{
public:
KPtyProcessPrivate()
{
}
std::unique_ptr<KPtyDevice> pty;
KPtyProcess::PtyChannels ptyChannels = KPtyProcess::NoChannels;
bool addUtmp = false;
};
KPtyProcess::KPtyProcess(QObject *parent)
: KPtyProcess(-1, parent)
{
}
KPtyProcess::KPtyProcess(int ptyMasterFd, QObject *parent)
: KProcess(parent)
, d_ptr(new KPtyProcessPrivate)
{
Q_D(KPtyProcess);
auto parentChildProcModifier = KProcess::childProcessModifier();
setChildProcessModifier([d, parentChildProcModifier]() {
d->pty->setCTty();
if (d->addUtmp) {
d->pty->login(KUser(KUser::UseRealUserID).loginName().toLocal8Bit().constData(), qgetenv("DISPLAY").constData());
}
if (d->ptyChannels & StdinChannel) {
dup2(d->pty->slaveFd(), 0);
}
if (d->ptyChannels & StdoutChannel) {
dup2(d->pty->slaveFd(), 1);
}
if (d->ptyChannels & StderrChannel) {
dup2(d->pty->slaveFd(), 2);
}
if (parentChildProcModifier) {
parentChildProcModifier();
}
});
d->pty = std::make_unique<KPtyDevice>(this);
if (ptyMasterFd == -1) {
d->pty->open();
} else {
d->pty->open(ptyMasterFd);
}
connect(this, &QProcess::stateChanged, this, [this](QProcess::ProcessState state) {
if (state == QProcess::NotRunning && d_ptr->addUtmp) {
d_ptr->pty->logout();
}
});
}
KPtyProcess::~KPtyProcess()
{
Q_D(KPtyProcess);
if (state() != QProcess::NotRunning && d->addUtmp) {
d->pty->logout();
disconnect(this, &QProcess::stateChanged, this, nullptr);
}
}
void KPtyProcess::setPtyChannels(PtyChannels channels)
{
Q_D(KPtyProcess);
d->ptyChannels = channels;
}
KPtyProcess::PtyChannels KPtyProcess::ptyChannels() const
{
Q_D(const KPtyProcess);
return d->ptyChannels;
}
void KPtyProcess::setUseUtmp(bool value)
{
Q_D(KPtyProcess);
d->addUtmp = value;
}
bool KPtyProcess::isUseUtmp() const
{
Q_D(const KPtyProcess);
return d->addUtmp;
}
KPtyDevice *KPtyProcess::pty() const
{
Q_D(const KPtyProcess);
return d->pty.get();
}
#include "moc_kptyprocess.cpp"
@@ -0,0 +1,150 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPTYPROCESS_H
#define KPTYPROCESS_H
#include <KProcess>
#include "kpty_export.h"
#include <memory>
class KPtyDevice;
class KPtyProcessPrivate;
/**
* This class extends KProcess by support for PTYs (pseudo TTYs).
*
* The PTY is opened as soon as the class is instantiated. Verify that
* it was opened successfully by checking that pty()->masterFd() is not -1.
*
* The PTY is always made the process' controlling TTY.
* Utmp registration and connecting the stdio handles to the PTY are optional.
*
* No attempt to integrate with QProcess' waitFor*() functions was made,
* for it is impossible. Note that execute() does not work with the PTY, too.
* Use the PTY device's waitFor*() functions or use it asynchronously.
*
* @note If you inherit from this class and use setChildProcessModifier() in
* the derived class, you must call the childProcessModifier() of KPtyProcess
* first (using setChildProcessModifier() in the derived class will "overwrite"
* the childProcessModifier() std::function that was previously set by KPtyProcess).
* For example:
* @code
* class MyProcess : public KPtyProcess
* {
* MyProcess()
* {
* auto parentChildProcModifier = KPtyProcess::childProcessModifier();
* setChildProcessModifier([parentChildProcModifier]() {
* // First call the parent class modifier function
* if (parentChildProcModifier) {
* parentChildProcModifier();
* }
* // Then whatever extra code you need to run
* ....
* ....
* });
* }
* @endcode
*
* @author Oswald Buddenhagen <ossi@kde.org>
*/
class KPTY_EXPORT KPtyProcess : public KProcess
{
Q_OBJECT
Q_DECLARE_PRIVATE(KPtyProcess)
public:
/**
* @see PtyChannels
*/
enum PtyChannelFlag {
NoChannels = 0, /**< The PTY is not connected to any channel. */
StdinChannel = 1, /**< Connect PTY to stdin. */
StdoutChannel = 2, /**< Connect PTY to stdout. */
StderrChannel = 4, /**< Connect PTY to stderr. */
AllOutputChannels = 6, /**< Connect PTY to all output channels. */
AllChannels = 7, /**< Connect PTY to all channels. */
};
/**
* Stores a combination of #PtyChannelFlag values.
*/
Q_DECLARE_FLAGS(PtyChannels, PtyChannelFlag)
/**
* Constructor
*/
explicit KPtyProcess(QObject *parent = nullptr);
/**
* Construct a process using an open pty master.
*
* @param ptyMasterFd an open pty master file descriptor.
* The process does not take ownership of the descriptor;
* it will not be automatically closed at any point.
*/
KPtyProcess(int ptyMasterFd, QObject *parent = nullptr);
/**
* Destructor
*/
~KPtyProcess() override;
/**
* Set to which channels the PTY should be assigned.
*
* This function must be called before starting the process.
*
* @param channels the output channel handling mode
*/
void setPtyChannels(PtyChannels channels);
/**
* Query to which channels the PTY is assigned.
*
* @return the output channel handling mode
*/
PtyChannels ptyChannels() const;
/**
* Set whether to register the process as a TTY login in utmp.
*
* Utmp is disabled by default.
* It should enabled for interactively fed processes, like terminal
* emulations.
*
* This function must be called before starting the process.
*
* @param value whether to register in utmp.
*/
void setUseUtmp(bool value);
/**
* Get whether to register the process as a TTY login in utmp.
*
* @return whether to register in utmp
*/
bool isUseUtmp() const;
/**
* Get the PTY device of this process.
*
* @return the PTY device
*/
KPtyDevice *pty() const;
private:
std::unique_ptr<KPtyProcessPrivate> const d_ptr;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KPtyProcess::PtyChannels)
#endif