feat: add KF6 Pty recipe source
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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 <termios.h> 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
|
||||
Reference in New Issue
Block a user