There are some curses based programs that are launched creating a process chain among them, using fork/exec/wait.
When the xterm is resized and there is only the first program running all works fine. But, when the second (or third) program is running:
savetty{};
endwin();
// forking
if (fork() != 0) {
// at parent
wait(&status);
} else {
// child
if (execlp(cmd, cmd, (char*) NULL) < 0) {
exit(1);
}
}
// child exited: parent refresh its screen
resetty(); refresh();
All programs seems to try refresh their screens at same time. From this point the screen becomes a real mess.
What need to be done in each program before exec the next program, to "freezes" curses until the wait returns ?
EDIT:
When I replace fork/exec/wait to system(), all works fine. But I cannot keep it (it was just a test), it's a big legacy system strongly dependent of fork/exec/wait system calls.
I also tried to run execlp("/bin/sh", "-c", cmd) and the problem is the same.
EDIT 2: A full sample code from scratch:
// Uncomment to switch from fork/exec/wait to system() function
//#define USE_SYSTEM
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <curses.h>
#include <term.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
void mybox(int y, int x, int height, int width);
void msg(char* format, ...);
void draw();
int myexec(char* cmd);
int spawn_new_instance(char* cmd);
char* argid = NULL;
int main(int argc, char* argv[]) {
initscr();
cbreak();
keypad(stdscr, TRUE);
noecho();
draw();
if (argc > 1) {
argid=argv[1];
msg("That is a child process");
}
int pid;
int key;
do {
key = getch();
switch(key) {
case KEY_RESIZE:
clear(); draw();
msg("KEY_RESIZE");
break;
case 'r':
case 'R':
pid = spawn_new_instance(argv[0]);
if (argid) {
#ifdef USE_SYSTEM
msg("Came back from system()");
#else
msg("Came back from pid %d", pid);
#endif
} else {
msg("Came back from pid %d - THAT IS THE ROOT PROCESS", pid);
}
break;
default:
msg("Unsupported key '%d'. Type '.' (dot) to exit", key);
}
} while (key != '.');
endwin();
}
void fullbox(void) {
mybox(0, 0, LINES, COLS);
}
void mybox(int y, int x, int height, int width) {
int x2 = x + width - 1;
int y2 = y + height - 1;
for (int ct = x; ct < x2; ct++) {
mvaddch(y, ct, ACS_HLINE);
mvaddch(y2, ct, ACS_HLINE);
}
for (int ct = y; ct < y2; ct++) {
mvaddch(ct, x, ACS_VLINE);
mvaddch(ct, x2, ACS_VLINE);
}
mvaddch(y, x, ACS_ULCORNER);
mvaddch(y, x2, ACS_URCORNER);
mvaddch(y2, x, ACS_LLCORNER);
mvaddch(y2, x2, ACS_LRCORNER);
refresh();
}
void msg(char* format, ...) {
for (int ct = 2; ct < COLS - 2; ct++) {
mvaddch(LINES-3, ct, ACS_CKBOARD);
}
char buffer[512];
va_list argptr;
va_start(argptr, format);
vsprintf(buffer, format, argptr);
int msglen = strlen(buffer) + 2;
int msgx = (COLS - msglen)/2;
mvprintw(LINES-3, msgx, " %s ", buffer);
}
void draw() {
mybox(0, 0, LINES, COLS);
char sbuf[128];
sprintf(sbuf, "PID: %d, LINES: %d, COLS: %d", getpid(), LINES, COLS);
int msglen = strlen(sbuf);
int msgy = (LINES - 1) / 2;
int msgx = (COLS - msglen)/2;
mvprintw(msgy, msgx, "%s", sbuf);
mybox(msgy-2, msgx-2, 1 + 4, msglen + 4);
mybox((LINES - LINES/3)/2, (COLS - COLS/3)/2, LINES/3, COLS/3);
mybox(LINES-4, 1, 3, COLS-2);
msg("Resize the terminal emulator, or type R to chain new process instance");
refresh();
}
int spawn_new_instance(char* cmd) {
savetty();
endwin();
int pid;
#ifdef USE_SYSTEM
char buf[512];
sprintf(buf, "%s child", cmd);
system(buf);
// we haven't pid using system()
pid=0;
#else
pid = myexec(cmd);
#endif
resetty();
refresh();
return pid;
}
int myexec(char* cmd) {
sigset_t blockSigchld;
sigset_t previousBlock;
sigaddset(&blockSigchld, SIGCHLD);
sigprocmask(SIG_BLOCK, &blockSigchld, &previousBlock);
int ret = 0, status = 0;
int retries = 4;
int pid;
while ((pid = fork()) == -1) {
if (errno == EAGAIN) {
if (--retries >= 0) {
sleep(1);
continue;
} else {
msg("Cannot open the process now.");
return -1;
}
} else if (errno == ENOMEM) {
msg("Not enough memory.");
return -1;
} else {
msg("Errno = %u", errno);
return -1;
}
}
if (pid != 0) { /* Parent */
ret = waitpid(pid, &status, 0);
sigprocmask(SIG_SETMASK, &previousBlock, (sigset_t *) 0);
if (ret == -1) {
return -1;
}
return pid;
} else { /* Child */
sigprocmask(SIG_SETMASK, &previousBlock, (sigset_t *) 0);
if (execlp(cmd, cmd, "child", (char*) NULL) < 0) {
exit(1);
}
}
}
Instructions:
- Type "R" to spawn a chained new instance of itself.
- Type "." (dot) to exit and return to parent process.
- While in the first (main) process, resize the terminal. Everything works fine.
- THE PROBLEM: Open a lot of instances (10 or more), resize the terminal. Look that it refresh/redrawn the screen multiple times, probably one for each instance running. Try to return to the parent, seems that from this point all them are trying to read de stdin and redraw its screen at same time, the real mess happens. If you have luck, you'll be able to press
CTRL+Ceither typepkill testprogram. - Try the above using system() function. The problem doesn't occur. For test purposes, at first line is a #define to easily switch from fork/exec/wait to system().
resetty(); refresh();-->>refresh(); resetty();on the last line? - joopwaitfor the child, and restore the handler afterwards - Karsten Koop