fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kpty / kpty.cpp
blobce2cf0e6ec1fb288399d461c3c69471cb3e6ef14
1 /*
3 This file is part of the KDE libraries
4 Copyright (C) 2002 Waldo Bastian <bastian@kde.org>
5 Copyright (C) 2002-2003,2007-2008 Oswald Buddenhagen <ossi@kde.org>
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
23 #include "kpty_p.h"
25 #include <config.h>
27 #ifdef __sgi
28 #define __svr4__
29 #endif
31 #ifdef __osf__
32 #define _OSF_SOURCE
33 #include <float.h>
34 #endif
36 #ifdef _AIX
37 #define _ALL_SOURCE
38 #endif
40 // __USE_XOPEN isn't defined by default in ICC
41 // (needed for ptsname(), grantpt() and unlockpt())
42 #ifdef __INTEL_COMPILER
43 # ifndef __USE_XOPEN
44 # define __USE_XOPEN
45 # endif
46 #endif
48 #include <sys/types.h>
49 #include <sys/ioctl.h>
50 #include <sys/time.h>
51 #include <sys/resource.h>
52 #include <sys/stat.h>
53 #include <sys/param.h>
55 #include <errno.h>
56 #include <fcntl.h>
57 #include <time.h>
58 #include <stdlib.h>
59 #include <stdio.h>
60 #include <string.h>
61 #include <unistd.h>
62 #include <grp.h>
64 #if defined(HAVE_PTY_H)
65 # include <pty.h>
66 #endif
68 #ifdef HAVE_LIBUTIL_H
69 # include <libutil.h>
70 #elif defined(HAVE_UTIL_H)
71 # include <util.h>
72 #endif
74 #ifdef HAVE_UTEMPTER
75 extern "C" {
76 # include <utempter.h>
78 #else
79 # include <utmp.h>
80 # ifdef HAVE_UTMPX
81 # include <utmpx.h>
82 # endif
83 # if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE)
84 # define _PATH_UTMPX _UTMPX_FILE
85 # endif
86 # if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE)
87 # define _PATH_WTMPX _WTMPX_FILE
88 # endif
89 #endif
91 /* for HP-UX (some versions) the extern C is needed, and for other
92 platforms it doesn't hurt */
93 extern "C" {
94 #include <termios.h>
95 #if defined(HAVE_TERMIO_H)
96 # include <termio.h> // struct winsize on some systems
97 #endif
100 #if defined (_HPUX_SOURCE)
101 # define _TERMIOS_INCLUDED
102 # include <bsdtty.h>
103 #endif
105 #ifdef HAVE_SYS_STROPTS_H
106 # include <sys/stropts.h> // Defines I_PUSH
107 # define _NEW_TTY_CTRL
108 #endif
110 #if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__)
111 # define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode)
112 #else
113 # if defined(_HPUX_SOURCE) || defined(__Lynx__) || defined (__CYGWIN__)
114 # define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode)
115 # else
116 # define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode)
117 # endif
118 #endif
120 #if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__)
121 # define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode)
122 #else
123 # if defined(_HPUX_SOURCE) || defined(__CYGWIN__)
124 # define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode)
125 # else
126 # define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode)
127 # endif
128 #endif
130 #include <kdebug.h>
131 #include <kstandarddirs.h> // findExe
132 #include <kde_file.h>
134 #include <QtCore/Q_PID>
136 #define TTY_GROUP "tty"
138 ///////////////////////
139 // private functions //
140 ///////////////////////
142 //////////////////
143 // private data //
144 //////////////////
146 KPtyPrivate::KPtyPrivate(KPty* parent) :
147 masterFd(-1), slaveFd(-1), ownMaster(true), q_ptr(parent)
151 KPtyPrivate::~KPtyPrivate()
155 #ifndef HAVE_OPENPTY
156 bool KPtyPrivate::chownpty(bool grant)
158 return !QProcess::execute(KStandardDirs::findExe("kgrantpty"),
159 QStringList() << (grant?"--grant":"--revoke") << QString::number(masterFd));
161 #endif
163 /////////////////////////////
164 // public member functions //
165 /////////////////////////////
167 KPty::KPty() :
168 d_ptr(new KPtyPrivate(this))
172 KPty::KPty(KPtyPrivate *d) :
173 d_ptr(d)
175 d_ptr->q_ptr = this;
178 KPty::~KPty()
180 close();
181 delete d_ptr;
184 bool KPty::open()
186 Q_D(KPty);
188 if (d->masterFd >= 0)
189 return true;
191 d->ownMaster = true;
193 QByteArray ptyName;
195 // Find a master pty that we can open ////////////////////////////////
197 // Because not all the pty animals are created equal, they want to
198 // be opened by several different methods.
200 // We try, as we know them, one by one.
202 #ifdef HAVE_OPENPTY
204 char ptsn[PATH_MAX];
205 if (::openpty( &d->masterFd, &d->slaveFd, ptsn, 0, 0))
207 d->masterFd = -1;
208 d->slaveFd = -1;
209 kWarning(175) << "Can't open a pseudo teletype";
210 return false;
212 d->ttyName = ptsn;
214 #else
216 #ifdef HAVE__GETPTY // irix
218 char *ptsn = _getpty(&d->masterFd, O_RDWR|O_NOCTTY, S_IRUSR|S_IWUSR, 0);
219 if (ptsn) {
220 d->ttyName = ptsn;
221 goto grantedpt;
224 #elif defined(HAVE_PTSNAME) || defined(TIOCGPTN)
226 #ifdef HAVE_POSIX_OPENPT
227 d->masterFd = ::posix_openpt(O_RDWR|O_NOCTTY);
228 #elif defined(HAVE_GETPT)
229 d->masterFd = ::getpt();
230 #elif defined(PTM_DEVICE)
231 d->masterFd = KDE_open(PTM_DEVICE, O_RDWR|O_NOCTTY);
232 #else
233 # error No method to open a PTY master detected.
234 #endif
235 if (d->masterFd >= 0)
237 #ifdef HAVE_PTSNAME
238 char *ptsn = ptsname(d->masterFd);
239 if (ptsn) {
240 d->ttyName = ptsn;
241 #else
242 int ptyno;
243 if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) {
244 char buf[32];
245 sprintf(buf, "/dev/pts/%d", ptyno);
246 d->ttyName = buf;
247 #endif
248 #ifdef HAVE_GRANTPT
249 if (!grantpt(d->masterFd))
250 goto grantedpt;
251 #else
252 goto gotpty;
253 #endif
255 ::close(d->masterFd);
256 d->masterFd = -1;
258 #endif // HAVE_PTSNAME || TIOCGPTN
260 // Linux device names, FIXME: Trouble on other systems?
261 for (const char* s3 = "pqrstuvwxyzabcde"; *s3; s3++)
263 for (const char* s4 = "0123456789abcdef"; *s4; s4++)
265 ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toAscii();
266 d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toAscii();
268 d->masterFd = KDE_open(ptyName.data(), O_RDWR);
269 if (d->masterFd >= 0)
271 #ifdef Q_OS_SOLARIS
272 /* Need to check the process group of the pty.
273 * If it exists, then the slave pty is in use,
274 * and we need to get another one.
276 int pgrp_rtn;
277 if (ioctl(d->masterFd, TIOCGPGRP, &pgrp_rtn) == 0 || errno != EIO) {
278 ::close(d->masterFd);
279 d->masterFd = -1;
280 continue;
282 #endif /* Q_OS_SOLARIS */
283 if (!access(d->ttyName.data(),R_OK|W_OK)) // checks availability based on permission bits
285 if (!geteuid())
287 struct group* p = getgrnam(TTY_GROUP);
288 if (!p)
289 p = getgrnam("wheel");
290 gid_t gid = p ? p->gr_gid : getgid ();
292 chown(d->ttyName.data(), getuid(), gid);
293 chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IWGRP);
295 goto gotpty;
297 ::close(d->masterFd);
298 d->masterFd = -1;
303 kWarning(175) << "Can't open a pseudo teletype";
304 return false;
306 gotpty:
307 KDE_struct_stat st;
308 if (KDE_stat(d->ttyName.data(), &st))
309 return false; // this just cannot happen ... *cough* Yeah right, I just
310 // had it happen when pty #349 was allocated. I guess
311 // there was some sort of leak? I only had a few open.
312 if (((st.st_uid != getuid()) ||
313 (st.st_mode & (S_IRGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH))) &&
314 !d->chownpty(true))
316 kWarning(175)
317 << "chownpty failed for device " << ptyName << "::" << d->ttyName
318 << "\nThis means the communication can be eavesdropped." << endl;
321 grantedpt:
323 #ifdef HAVE_REVOKE
324 revoke(d->ttyName.data());
325 #endif
327 #ifdef HAVE_UNLOCKPT
328 unlockpt(d->masterFd);
329 #elif defined(TIOCSPTLCK)
330 int flag = 0;
331 ioctl(d->masterFd, TIOCSPTLCK, &flag);
332 #endif
334 d->slaveFd = KDE_open(d->ttyName.data(), O_RDWR | O_NOCTTY);
335 if (d->slaveFd < 0)
337 kWarning(175) << "Can't open slave pseudo teletype";
338 ::close(d->masterFd);
339 d->masterFd = -1;
340 return false;
343 #if (defined(__svr4__) || defined(__sgi__))
344 // Solaris
345 ioctl(d->slaveFd, I_PUSH, "ptem");
346 ioctl(d->slaveFd, I_PUSH, "ldterm");
347 #endif
349 #endif /* HAVE_OPENPTY */
351 fcntl(d->masterFd, F_SETFD, FD_CLOEXEC);
352 fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC);
354 return true;
357 bool KPty::open(int fd)
359 #if !defined(HAVE_PTSNAME) && !defined(TIOCGPTN)
360 kWarning(175) << "Unsupported attempt to open pty with fd" << fd;
361 return false;
362 #else
363 Q_D(KPty);
365 if (d->masterFd >= 0) {
366 kWarning(175) << "Attempting to open an already open pty";
367 return false;
370 d->ownMaster = false;
372 # ifdef HAVE_PTSNAME
373 char *ptsn = ptsname(fd);
374 if (ptsn) {
375 d->ttyName = ptsn;
376 # else
377 int ptyno;
378 if (!ioctl(fd, TIOCGPTN, &ptyno)) {
379 char buf[32];
380 sprintf(buf, "/dev/pts/%d", ptyno);
381 d->ttyName = buf;
382 # endif
383 } else {
384 kWarning(175) << "Failed to determine pty slave device for fd" << fd;
385 return false;
388 d->masterFd = fd;
389 if (!openSlave()) {
390 d->masterFd = -1;
391 return false;
394 return true;
395 #endif
398 void KPty::closeSlave()
400 Q_D(KPty);
402 if (d->slaveFd < 0)
403 return;
404 ::close(d->slaveFd);
405 d->slaveFd = -1;
408 bool KPty::openSlave()
410 Q_D(KPty);
412 if (d->slaveFd >= 0)
413 return true;
414 if (d->masterFd < 0) {
415 kWarning(175) << "Attempting to open pty slave while master is closed";
416 return false;
418 d->slaveFd = KDE_open(d->ttyName.data(), O_RDWR | O_NOCTTY);
419 if (d->slaveFd < 0) {
420 kWarning(175) << "Can't open slave pseudo teletype";
421 return false;
423 fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC);
424 return true;
427 void KPty::close()
429 Q_D(KPty);
431 if (d->masterFd < 0)
432 return;
433 closeSlave();
434 if (d->ownMaster) {
435 #ifndef HAVE_OPENPTY
436 // don't bother resetting unix98 pty, it will go away after closing master anyway.
437 if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) {
438 if (!geteuid()) {
439 struct stat st;
440 if (!stat(d->ttyName.data(), &st)) {
441 chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1);
442 chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
444 } else {
445 fcntl(d->masterFd, F_SETFD, 0);
446 d->chownpty(false);
449 #endif
450 ::close(d->masterFd);
452 d->masterFd = -1;
455 void KPty::setCTty()
457 Q_D(KPty);
459 // Setup job control //////////////////////////////////
461 // Become session leader, process group leader,
462 // and get rid of the old controlling terminal.
463 setsid();
465 // make our slave pty the new controlling terminal.
466 #ifdef TIOCSCTTY
467 ioctl(d->slaveFd, TIOCSCTTY, 0);
468 #else
469 // __svr4__ hack: the first tty opened after setsid() becomes controlling tty
470 ::close(KDE_open(d->ttyName, O_WRONLY, 0));
471 #endif
473 // make our new process group the foreground group on the pty
474 int pgrp = getpid();
475 #if defined(_POSIX_VERSION) || defined(__svr4__)
476 tcsetpgrp(d->slaveFd, pgrp);
477 #elif defined(TIOCSPGRP)
478 ioctl(d->slaveFd, TIOCSPGRP, (char *)&pgrp);
479 #endif
482 void KPty::login(const char *user, const char *remotehost)
484 #ifdef HAVE_UTEMPTER
485 Q_D(KPty);
487 addToUtmp(d->ttyName, remotehost, d->masterFd);
488 Q_UNUSED(user);
489 #else
490 # ifdef HAVE_UTMPX
491 struct utmpx l_struct;
492 # else
493 struct utmp l_struct;
494 # endif
495 memset(&l_struct, 0, sizeof(l_struct));
496 // note: strncpy without terminators _is_ correct here. man 4 utmp
498 if (user)
499 strncpy(l_struct.ut_name, user, sizeof(l_struct.ut_name));
501 if (remotehost) {
502 strncpy(l_struct.ut_host, remotehost, sizeof(l_struct.ut_host));
503 # ifdef HAVE_STRUCT_UTMP_UT_SYSLEN
504 l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host));
505 # endif
508 # ifndef __GLIBC__
509 Q_D(KPty);
510 const char *str_ptr = d->ttyName.data();
511 if (!memcmp(str_ptr, "/dev/", 5))
512 str_ptr += 5;
513 strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
514 # ifdef HAVE_STRUCT_UTMP_UT_ID
515 strncpy(l_struct.ut_id,
516 str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id),
517 sizeof(l_struct.ut_id));
518 # endif
519 # endif
521 # ifdef HAVE_UTMPX
522 gettimeofday(&l_struct.ut_tv, 0);
523 # else
524 l_struct.ut_time = time(0);
525 # endif
527 # ifdef HAVE_LOGIN
528 # ifdef HAVE_LOGINX
529 ::loginx(&l_struct);
530 # else
531 ::login(&l_struct);
532 # endif
533 # else
534 # ifdef HAVE_STRUCT_UTMP_UT_TYPE
535 l_struct.ut_type = USER_PROCESS;
536 # endif
537 # ifdef HAVE_STRUCT_UTMP_UT_PID
538 l_struct.ut_pid = getpid();
539 # ifdef HAVE_STRUCT_UTMP_UT_SESSION
540 l_struct.ut_session = getsid(0);
541 # endif
542 # endif
543 # ifdef HAVE_UTMPX
544 utmpxname(_PATH_UTMPX);
545 setutxent();
546 pututxline(&l_struct);
547 endutxent();
548 updwtmpx(_PATH_WTMPX, &l_struct);
549 # else
550 utmpname(_PATH_UTMP);
551 setutent();
552 pututline(&l_struct);
553 endutent();
554 updwtmp(_PATH_WTMP, &l_struct);
555 # endif
556 # endif
557 #endif
560 void KPty::logout()
562 #ifdef HAVE_UTEMPTER
563 Q_D(KPty);
565 removeLineFromUtmp(d->ttyName, d->masterFd);
566 #else
567 Q_D(KPty);
569 const char *str_ptr = d->ttyName.data();
570 if (!memcmp(str_ptr, "/dev/", 5))
571 str_ptr += 5;
572 # ifdef __GLIBC__
573 else {
574 const char *sl_ptr = strrchr(str_ptr, '/');
575 if (sl_ptr)
576 str_ptr = sl_ptr + 1;
578 # endif
579 # ifdef HAVE_LOGIN
580 # ifdef HAVE_LOGINX
581 ::logoutx(str_ptr, 0, DEAD_PROCESS);
582 # else
583 ::logout(str_ptr);
584 # endif
585 # else
586 # ifdef HAVE_UTMPX
587 struct utmpx l_struct, *ut;
588 # else
589 struct utmp l_struct, *ut;
590 # endif
591 memset(&l_struct, 0, sizeof(l_struct));
593 strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
595 # ifdef HAVE_UTMPX
596 utmpxname(_PATH_UTMPX);
597 setutxent();
598 if ((ut = getutxline(&l_struct))) {
599 # else
600 utmpname(_PATH_UTMP);
601 setutent();
602 if ((ut = getutline(&l_struct))) {
603 # endif
604 memset(ut->ut_name, 0, sizeof(*ut->ut_name));
605 memset(ut->ut_host, 0, sizeof(*ut->ut_host));
606 # ifdef HAVE_STRUCT_UTMP_UT_SYSLEN
607 ut->ut_syslen = 0;
608 # endif
609 # ifdef HAVE_STRUCT_UTMP_UT_TYPE
610 ut->ut_type = DEAD_PROCESS;
611 # endif
612 # ifdef HAVE_UTMPX
613 gettimeofday(&(ut->ut_tv), 0);
614 pututxline(ut);
616 endutxent();
617 # else
618 ut->ut_time = time(0);
619 pututline(ut);
621 endutent();
622 # endif
623 # endif
624 #endif
627 // XXX Supposedly, tc[gs]etattr do not work with the master on Solaris.
628 // Please verify.
630 bool KPty::tcGetAttr(struct ::termios *ttmode) const
632 Q_D(const KPty);
634 return _tcgetattr(d->masterFd, ttmode) == 0;
637 bool KPty::tcSetAttr(struct ::termios *ttmode)
639 Q_D(KPty);
641 return _tcsetattr(d->masterFd, ttmode) == 0;
644 bool KPty::setWinSize(int lines, int columns)
646 Q_D(KPty);
648 struct winsize winSize;
649 memset(&winSize, 0, sizeof(winSize));
650 winSize.ws_row = (unsigned short)lines;
651 winSize.ws_col = (unsigned short)columns;
652 return ioctl(d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0;
655 bool KPty::setEcho(bool echo)
657 struct ::termios ttmode;
658 if (!tcGetAttr(&ttmode))
659 return false;
660 if (!echo)
661 ttmode.c_lflag &= ~ECHO;
662 else
663 ttmode.c_lflag |= ECHO;
664 return tcSetAttr(&ttmode);
667 const char *KPty::ttyName() const
669 Q_D(const KPty);
671 return d->ttyName.data();
674 int KPty::masterFd() const
676 Q_D(const KPty);
678 return d->masterFd;
681 int KPty::slaveFd() const
683 Q_D(const KPty);
685 return d->slaveFd;