/* remote.c - core analysis suite
 *
 * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
 * Copyright (C) 2002, 2003, 2004, 2005, 2009, 2011, 2018 David Anderson
 * Copyright (C) 2002, 2003, 2004, 2005, 2009, 2011, 2018 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include "defs.h"
#include <netinet/in.h>
#include <netdb.h>
#include <net/if_arp.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <elf.h>

#define FAILMSG "FAIL "
#define DONEMSG "DONE "
#define DATAMSG "DATA "

#define DATA_HDRSIZE (13)   /* strlen("XXXX ") + strlen("0131072") + NULL */

#define MAXRECVBUFSIZE (131072) 
#define READBUFSIZE    (MAXRECVBUFSIZE+DATA_HDRSIZE)

#ifdef DAEMON
/*
 *  The remote daemon.  
 */

static int daemon_init(void);
static ulong daemon_htol(char *);
static int daemon_is_elf_file(char *);
static int daemon_mount_point(char *);
static int daemon_find_booted_kernel(char *);
static char **daemon_build_searchdirs(int);
static int daemon_is_directory(char *);
static int daemon_file_readable(char *);
static int daemon_parse_line(char *, char **);
static char *daemon_clean_line(char *);
int console(char *, ...);
static void daemon_socket_options(int);
static char *no_debugging_symbols_found(char *);
static ulong daemon_filesize(int);
static int daemon_find_module(char *, char *, char *);
static int daemon_search_directory_tree(char *, char *, char *);
static int daemon_file_exists(char *, struct stat *);
static int daemon_checksum(char *, long *);
static void daemon_send(void *, int);
static int daemon_proc_version(char *);
static void handle_connection(int);

struct remote_context {
        int sock;
        int remdebug; 
        char *remdebugfile;
} remote_context = { 0, 0, "/dev/null" };

struct remote_context *rc = &remote_context;

int
main(int argc, char **argv)
{
        int c, sockfd, newsockfd, clilen;
        struct sockaddr_in serv_addr, cli_addr;
        struct hostent *hp;
        ushort tcp_port;
        char hostname[MAXHOSTNAMELEN];

	tcp_port = 0;
        optind = 0;
        while ((c = getopt(argc, argv, "vd:")) > 0) {
                switch (c)
                {
		case 'v':
			printf("%s %s\n", basename(argv[0]), 
				/* BASELEVEL_REVISION */ "(deprecated)");
			exit(0);

                case 'd':
			rc->remdebug++;
			rc->remdebugfile = optarg;
			break;
		}
	}

	console("<parent daemon %d initiated>\n", getpid());

	while (argv[optind]) {
		if (!tcp_port)
                	tcp_port = (ushort)atoi(argv[optind]); 
		optind++;
	}

	console("port: %d\n", tcp_port);

        if (gethostname(hostname, MAXHOSTNAMELEN) < 0) {
		console("gethostname failed: %s\n", strerror(errno));
                perror("gethostname");
                exit(1);
        }

	console("hostname: %s\n", hostname);

        if ((hp = gethostbyname(hostname)) == NULL) {
		console("gethostbyname failed: %s\n", hstrerror(h_errno));
                perror("gethostbyname");
                exit(1);
        }

	console("attempting daemon_init...\n");

        if (!daemon_init())
                exit(1);

	console("<daemon %d initiated>\n", getpid());

        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
                exit(1);

        BZERO((char *)&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        BCOPY(hp->h_addr, (char *)&serv_addr.sin_addr, hp->h_length);
        serv_addr.sin_port = htons(tcp_port);

        daemon_socket_options(sockfd);

        if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
		console("%d: bind failed: %s\n", getpid(), strerror(errno));
                exit(1);
	}

        if (listen(sockfd, 5) < 0) {
		console("%d: listen failed: %s\n", getpid(), strerror(errno));
                exit(1);
	}

        for (;;) {
                clilen = sizeof(cli_addr);

                if ((newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr,
                    &clilen)) < 0) {
			console("%d: accept failed: %s\n", getpid(), 
				strerror(errno));
                        exit(1);
		}

                switch (fork())
                {
                case -1: exit(1);

                case 0:  close(sockfd);
                         handle_connection(newsockfd);
                         exit(0);

                default:
                         close(newsockfd);
                         break;
                }

                close(newsockfd);
        }
}

/*
 *  This probably doesn't do much, but it might reduce the acknowledge
 *  negotiations somewhat. (?)
 */
static void
daemon_socket_options(int sockfd)
{
        int nodelay;

        if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
            (char *)&nodelay, sizeof(nodelay)) < 0) 
                console("TCP_NODELAY setsockopt error\n");
}

/*
 *  This is the child daemon that handles the incoming requests.
 */
#define MAX_REMOTE_FDS  (10)

static void
handle_connection(int sock)
{
	int i;
	char recvbuf[BUFSIZE];
	char savebuf[BUFSIZE];
	char sendbuf[BUFSIZE];
	char buf1[BUFSIZE];
	char readbuf[READBUFSIZE+1];
	char *file;
	FILE *tmp, *pipe;
	char *p1, *p2, *p3;
	size_t cnt;
	int fds[MAX_REMOTE_FDS];
	int mfd;
	ulong addr, total, reqsize, bufsize;
        fd_set rfds;
        int len, first, retval, done;
	struct stat sbuf;

	rc->sock = sock;

	console("< new connection >\n");

	for (i = 0; i < MAX_REMOTE_FDS; i++)
		fds[i] = -1;

	while (TRUE) {

                FD_ZERO(&rfds);
                FD_SET(sock, &rfds);
                retval = select(sock+1, &rfds, NULL, NULL, NULL);

		BZERO(sendbuf, BUFSIZE);
		BZERO(recvbuf, BUFSIZE);

		switch (read(sock, recvbuf, BUFSIZE-1))
		{
		case -1:
			console("[read returned -1]\n");
			continue;
		case 0:
			console("[read returned 0]\n");
			return;
		default:
			console("[%s]: ", recvbuf);
			break;
		}

		if (STRNEQ(recvbuf, "OPEN ")) {

			strcpy(sendbuf, recvbuf);
			p1 = strtok(recvbuf, " ");  /* OPEN */
			file = strtok(NULL, " ");   /* filename */
			
			for (i = 0; i < MAX_REMOTE_FDS; i++) {
				if (fds[i] == -1)
					break;
			}

			if (i < MAX_REMOTE_FDS) { 
	        		if ((fds[i] = open(file, O_RDWR)) < 0) {
				        if ((fds[i] = open(file, O_RDONLY)) < 0)
						strcat(sendbuf, " <FAIL>");
					else {
						sprintf(buf1, 
						    " %d O_RDONLY %ld", fds[i], 
						    daemon_filesize(fds[i]));
						strcat(sendbuf, buf1);
					}
				} else {
					sprintf(buf1, " %d O_RDWR %ld", fds[i], 
						daemon_filesize(fds[i]));
					strcat(sendbuf, buf1);
				}
			} else 
				strcat(sendbuf, " <FAIL>");

			console("[%s]\n", sendbuf);

                        daemon_send(sendbuf, strlen(sendbuf));
			continue;

                } else if (STRNEQ(recvbuf, "READ_LIVE ")) {

                        strcpy(savebuf, recvbuf);

                        p1 = strtok(recvbuf, " ");   /* READ_LIVE */
                        p1 = strtok(NULL, " ");      /* filename id */
                        p2 = strtok(NULL, " ");      /* address */
                        p3 = strtok(NULL, " ");      /* length */

                        addr = daemon_htol(p2);
                        len = atoi(p3);
                        mfd = atoi(p1);
			errno = 0;

                        BZERO(readbuf, READBUFSIZE);

                        if (lseek(mfd, addr, SEEK_SET) == -1) 
                                len = 0;        
                        else if (read(mfd, &readbuf[DATA_HDRSIZE], len) != len) 
                                len = 0;

                        if (!len) {
				sprintf(readbuf, "%s%07ld", FAILMSG, 
					(ulong)errno);
                                console("[%s]\n", readbuf);
                        } else {
				sprintf(readbuf, "%s%07ld", DONEMSG,(ulong)len);
                                console("(%ld)\n", len);
			}

                        daemon_send(readbuf, len+DATA_HDRSIZE);

                        continue;

                } else if (STRNEQ(recvbuf, "READ_NETDUMP ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* READ_NETDUMP */
                        p2 = strtok(NULL, " ");      /* address */
                        p3 = strtok(NULL, " ");      /* length */

                        addr = daemon_htol(p2);
                        len = atoi(p3);

                        BZERO(readbuf, READBUFSIZE);
                        errno = 0;

                        if ((len = read_netdump(UNUSED,
                            &readbuf[DATA_HDRSIZE], len, UNUSED, addr)) < 0)
                                len = 0;

                        if (len) {
                                sprintf(readbuf, "%s%07ld", DONEMSG,(ulong)len);                                console("(%ld)\n", (ulong)len);
                        } else {
                                sprintf(readbuf, "%s%07ld", FAILMSG,
                                        (ulong)errno);
                                console("[%s]\n", readbuf);
                        }

                        daemon_send(readbuf, len+DATA_HDRSIZE);
                        continue;

                } else if (STRNEQ(recvbuf, "READ_MCLXCD ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* READ_MCLXCD */
                        p2 = strtok(NULL, " ");      /* address */
                        p3 = strtok(NULL, " ");      /* length */

                        addr = daemon_htol(p2);
                        len = atoi(p3);
			errno = 0;

                        BZERO(readbuf, READBUFSIZE);

        		if (vas_lseek(addr, SEEK_SET))
				len = 0;
        		else if (vas_read((void *)
				&readbuf[DATA_HDRSIZE], len) != len)
				len = 0;

                        if (len) {
				sprintf(readbuf, "%s%07ld", DONEMSG, 
					(ulong)len);
                                console("(%ld)\n", (ulong)len);
			} else {
				sprintf(readbuf, "%s%07ld", FAILMSG, 
					(ulong)errno);
                                console("[%s]\n", readbuf);
                        } 

                        daemon_send(readbuf, len+DATA_HDRSIZE);

                        continue;

                } else if (STRNEQ(recvbuf, "CLOSE ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* SIZE */
                        p1 = strtok(NULL, " ");      /* filename id */
                        mfd = atoi(p1);

                        for (i = retval = 0; i < MAX_REMOTE_FDS; i++) {
                                if (fds[i] == mfd) {
					close(mfd);
					fds[i] = -1;
					retval = TRUE;
                                        break;
				}
                        }

			sprintf(sendbuf, "%s%s", savebuf, 
				retval ? " OK" : " <FAIL>");
                        console("[%s]\n", sendbuf);

                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

		} else if (STRNEQ(recvbuf, "READ ")) {

			strcpy(savebuf, recvbuf);

			p1 = strtok(recvbuf, " ");   /* READ */
			p1 = strtok(NULL, " ");      /* filename id */
			p2 = strtok(NULL, " ");      /* address */
			p3 = strtok(NULL, " ");      /* length */

			addr = daemon_htol(p2);
			len = atoi(p3);
			mfd = atoi(p1);

			BZERO(readbuf, READBUFSIZE);

			if (lseek(mfd, addr, SEEK_SET) == -1) 
				len = 0;	
			else if (read(mfd, readbuf, len) != len) 
				len = 0;
			
			if (!len) {
				sprintf(readbuf, "%s <FAIL>", savebuf);
				len = strlen(readbuf);
				console("[%s]\n", readbuf);
			} else
				console("(%ld)\n", len);

			daemon_send(readbuf, len);

			continue;

		} else if (STRNEQ(recvbuf, "MACHINE_PID")) {

                        sprintf(sendbuf, "%s %s %d", 
				recvbuf, MACHINE_TYPE, getpid());
                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

		} else if (STRNEQ(recvbuf, "TYPE ")) {

			strcpy(savebuf, recvbuf);
			p1 = strtok(recvbuf, " ");   /* TYPE */
			file = strtok(NULL, " ");    /* filename */

        		if (stat(file, &sbuf) < 0)
				sprintf(sendbuf, "%s <FAIL>", savebuf);
			else if (daemon_is_elf_file(file))
				sprintf(sendbuf, "%s ELF", savebuf);
			else if (STREQ(file, "/dev/mem"))
				sprintf(sendbuf, "%s DEVMEM", savebuf);
			else if (is_netdump(file, NETDUMP_REMOTE))
				sprintf(sendbuf, "%s NETDUMP", savebuf);
			else if (is_mclx_compressed_dump(file))
				sprintf(sendbuf, "%s MCLXCD", savebuf);
			else if (is_lkcd_compressed_dump(file))
				sprintf(sendbuf, "%s LKCD", savebuf);
                        else if (is_s390_dump(file))
                                sprintf(sendbuf, "%s S390D", savebuf);
			else
				sprintf(sendbuf, "%s UNSUPPORTED", savebuf);

			console("[%s]\n", sendbuf);
			
                        daemon_send(sendbuf, strlen(sendbuf));
			continue;

                } else if (STRNEQ(recvbuf, "LINUX_VERSION ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* LINUX_VERSION */
                        file = strtok(NULL, " ");    /* filename */

			sprintf(readbuf, 
			    "/usr/bin/strings %s | grep 'Linux version'", 
				file);

        		if ((pipe = popen(readbuf, "r"))) {
				BZERO(readbuf, BUFSIZE);
        			if (fread(readbuf, sizeof(char), BUFSIZE-1,
				    pipe) > 0) 
					strcpy(sendbuf, readbuf);
				else
					sprintf(sendbuf, "%s <FAIL>", savebuf);
				pclose(pipe);
			} else 
				sprintf(sendbuf, "%s <FAIL>", savebuf);

                        console("[%s] (%d)\n", sendbuf, strlen(sendbuf));

                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

		} else if (STRNEQ(recvbuf, "READ_GZIP ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* READ_GZIP */
			p1 = strtok(NULL, " ");      /* bufsize */
			bufsize = atol(p1);
                        file = strtok(NULL, " ");    /* filename */

			errno = 0;
			reqsize = bufsize - DATA_HDRSIZE;

                        sprintf(readbuf, "/usr/bin/gzip -c %s", file);

                        if ((pipe = popen(readbuf, "r")) == NULL) {
				sprintf(readbuf, "%s%07ld", FAILMSG, 
					(ulong)errno);
                                console("[%s]\n", readbuf);
                        	daemon_send(readbuf, DATA_HDRSIZE);
				continue;
			}

			errno = cnt = done = total = first = 0;

        		while (!done) {
                        	BZERO(readbuf, READBUFSIZE);

				cnt = fread(&readbuf[DATA_HDRSIZE], 
					sizeof(char), reqsize, pipe);

				total += cnt;

				if (feof(pipe)) {
					sprintf(readbuf, "%s%07ld", 
						DONEMSG, (ulong)cnt);
					done = TRUE;
				} else if (ferror(pipe)) {
					sprintf(readbuf, "%s%07ld", FAILMSG,
						(ulong)errno);
					done = TRUE;
				} else
					sprintf(readbuf, "%s%07ld", 
						DATAMSG, (ulong)cnt);

				console("%s[%s]\n", !first++ ? "\n" : "",
					readbuf);

				daemon_send(readbuf, bufsize);
			}

			console("GZIP total: %ld\n", total);

			pclose(pipe);
			continue;

		} else if (STRNEQ(recvbuf, "PROC_VERSION")) {

			BZERO(readbuf, READBUFSIZE);

			if (!daemon_proc_version(readbuf))
				sprintf(readbuf, "%s <FAIL>", recvbuf);

			console("[%s]\n", readbuf);

                        daemon_send(readbuf, strlen(readbuf));
			continue;

                } else if (STRNEQ(recvbuf, "DEBUGGING_SYMBOLS ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* DEBUGGING */
                        p2 = strtok(NULL, " ");      /* filename */

			sprintf(sendbuf, "%s %s", savebuf, 
				no_debugging_symbols_found(p2));
                        console("[%s]\n", sendbuf);

                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "PAGESIZE ")) {

			if (strstr(recvbuf, "LIVE")) 
                        	sprintf(sendbuf, "%s %d", recvbuf, 
					(uint)getpagesize());
                        else if (strstr(recvbuf, "NETDUMP"))
                                sprintf(sendbuf, "%s %d", recvbuf,
                                        (uint)netdump_page_size());
			else if (strstr(recvbuf, "MCLXCD")) 
                        	sprintf(sendbuf, "%s %d", recvbuf, 
					(uint)mclx_page_size());
                        else if (strstr(recvbuf, "LKCD")) 
                                sprintf(sendbuf, "%s %d", recvbuf,
                                        (uint)lkcd_page_size());
                        else if (strstr(recvbuf, "S390D"))
                                sprintf(sendbuf, "%s %d", recvbuf,
                                        s390_page_size());

                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "FIND_BOOTED_KERNEL")) {

			BZERO(readbuf, READBUFSIZE);
			if (daemon_find_booted_kernel(readbuf))
				sprintf(sendbuf, "%s %s", recvbuf, readbuf);
			else
				sprintf(sendbuf, "%s <FAIL>", recvbuf);

			console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

		} else if (STRNEQ(recvbuf, "FIND_MODULE ")) {

                        strcpy(savebuf, recvbuf);

        		strtok(recvbuf, " ");           /* FIND_MODULE */
        		p1 = strtok(NULL, " ");         /* release */
        		p2 = strtok(NULL, " ");         /* module */

			if (daemon_find_module(p1, p2, buf1)) {
				if (daemon_checksum(buf1, &total))
					sprintf(sendbuf, "%s %s %lx", 
						savebuf, buf1, total);
				else
					sprintf(sendbuf, "%s %s %lx", 
						savebuf, buf1, 
						(ulong)0xdeadbeef);
			} else
				sprintf(sendbuf, "%s <FAIL>", savebuf);

			console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "SUM ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* SUM */
                        p2 = strtok(NULL, " ");      /* filename */

			if (daemon_checksum(p2, &total))
                        	sprintf(sendbuf, "%s %lx", savebuf, total);
			else
                        	sprintf(sendbuf, "%s <FAIL>", savebuf);

                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "MEMORY ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* MEMORY */
                        p2 = strtok(NULL, " ");      /* USED or FREE */
                        p3 = strtok(NULL, " ");      /* MCLXCD, LKCD, etc. */

			if (STREQ(p2, "FREE")) {
                                if (STREQ(p3, "NETDUMP")) 
                                        retval = netdump_free_memory();
				else if (STREQ(p3, "MCLXCD"))
					retval = vas_free_memory(NULL);
				else if (STREQ(p3, "LKCD")) 
					retval = lkcd_free_memory();
                                else if (STREQ(p3, "S390D")) 
                                        retval = s390_free_memory();
			}

			if (STREQ(p2, "USED")) {
				if (STREQ(p3, "NETDUMP"))
					retval = netdump_memory_used();
				else if (STREQ(p3, "MCLXCD"))
					retval = vas_memory_used();
				else if (STREQ(p3, "LKCD"))
					retval = lkcd_memory_used();
                                else if (STREQ(p3, "S390D"))
                                        retval = s390_memory_used();
			}

			sprintf(sendbuf, "%s %d", savebuf, retval);
			console("[%s]\n", sendbuf);
			daemon_send(sendbuf, strlen(sendbuf));
			continue;

                } else if (STRNEQ(recvbuf, "MEMORY_DUMP")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* MEMORY_DUMP */
                        p1 = strtok(NULL, " ");      /* bufsize */
                        p2 = strtok(NULL, " ");      /* MCLXCD, LKCD, etc. */
                        bufsize = atol(p1);

			reqsize = bufsize - DATA_HDRSIZE;
			errno = 0;

			if ((tmp = tmpfile()) == NULL) {
				sprintf(readbuf, "%s%07ld", FAILMSG, 
					(ulong)errno);
                                console("[%s]\n", readbuf);
                                daemon_send(readbuf, DATA_HDRSIZE);
                                continue;
			}

			if (STREQ(p2, "NETDUMP")) 
				retval = netdump_memory_dump(tmp);
			else if (STREQ(p2, "MCLXCD"))
				vas_memory_dump(tmp);
			else if (STREQ(p2, "LKCD")) 
				lkcd_memory_dump(tmp);
			else if (STREQ(p2, "LKCD_VERBOSE")) {
				set_lkcd_fp(tmp);
				dump_lkcd_environment(0);
				set_lkcd_fp(NULL);
			} else if (STREQ(p2, "S390D"))
                                s390_memory_dump(tmp);

			rewind(tmp);
			errno = cnt = done = total = first = 0;

        		while (!done) {
                        	BZERO(readbuf, READBUFSIZE);

				cnt = fread(&readbuf[DATA_HDRSIZE], 
					sizeof(char), reqsize, tmp);

				total += cnt;

				if (feof(tmp)) {
					sprintf(readbuf, "%s%07ld", 
						DONEMSG, (ulong)cnt);
					done = TRUE;
				} else if (ferror(tmp)) {
					sprintf(readbuf, "%s%07ld", FAILMSG, 
						(ulong)errno);
					done = TRUE;
				} else
					sprintf(readbuf, "%s%07ld", 
						DATAMSG, (ulong)cnt);

				console("%s[%s]\n", !first++ ? "\n" : "",
					readbuf);
                        	daemon_send(readbuf, bufsize);
			}

			console("MEMORY_DUMP total: %ld\n", total);

			fclose(tmp);
			continue;

               } else if (STRNEQ(recvbuf, "NETDUMP_INIT ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* NETDUMP_INIT */
                        p2 = strtok(NULL, " ");      /* fd */
                        p3 = strtok(NULL, " ");      /* dumpfile */

                        mfd = atoi(p2);
                        for (i = 0; i < MAX_REMOTE_FDS; i++) {
                                if (fds[i] == mfd) {
                                        close(mfd);
                                        fds[i] = -1;
                                        break;
                                }
                        }

                        sprintf(sendbuf, "%s %s", savebuf,
                            netdump_init(p3, NULL) ? "OK" : "<FAIL>");

                        if ((addr = get_netdump_panic_task())) {
                                sprintf(readbuf, "\npanic_task: %lx\n", addr);
                                strcat(sendbuf, readbuf);
                        }

                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "LKCD_DUMP_INIT ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* LKCD_DUMP_INIT */
                        p2 = strtok(NULL, " ");      /* fd */
                        p3 = strtok(NULL, " ");      /* dumpfile */

			sprintf(sendbuf, "%s %s", savebuf,
			    lkcd_dump_init(NULL, atoi(p2), p3) ? 
				"OK" : "<FAIL>");

			if ((addr = get_lkcd_panic_task())) {
				sprintf(readbuf, "\npanic_task: %lx\n", addr);
				strcat(sendbuf, readbuf);
			}
			readbuf[0] = NULLCHAR;
			get_lkcd_panicmsg(readbuf);
			if (strlen(readbuf)) {
				strcat(sendbuf, "panicmsg: ");
				strcat(sendbuf, readbuf);
			}

                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "READ_LKCD ")) {

                        strcpy(savebuf, recvbuf);

                        p1 = strtok(recvbuf, " ");   /* READ_LKCD */
                        p1 = strtok(NULL, " ");      /* filename id */
                        p2 = strtok(NULL, " ");      /* address */
                        p3 = strtok(NULL, " ");      /* length */

                        mfd = atoi(p1);
                        addr = daemon_htol(p2);
                        len = atoi(p3);

                        BZERO(readbuf, READBUFSIZE);
			errno = 0;

                        if (!lkcd_lseek(addr))
                                len = 0;         
                        else if (lkcd_read((void *)
				&readbuf[DATA_HDRSIZE], len) != len)
                                len = 0;

			if (len) {
				sprintf(readbuf, "%s%07ld", DONEMSG,(ulong)len);
                                console("(%ld)\n", (ulong)len);
			} else {
				sprintf(readbuf, "%s%07ld", FAILMSG, 
					(ulong)errno);
                                console("[%s]\n", readbuf);
                        }

                        daemon_send(readbuf, len+DATA_HDRSIZE);
                        continue;

                } else if (STRNEQ(recvbuf, "S390_DUMP_INIT ")) {
                        
                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* S390_DUMP_INIT */
                        p2 = strtok(NULL, " ");      /* fd */ 
                        p3 = strtok(NULL, " ");      /* filename */

			mfd = atoi(p2);
			for (i = 0; i < MAX_REMOTE_FDS; i++) {
                                if (fds[i] == mfd) {
                                        close(mfd);
                                        fds[i] = -1;
                                        break;
                                }
                        }
                        
                        sprintf(sendbuf, "%s %s", savebuf,
                            s390_dump_init(p3) ? "OK" : "<FAIL>");

                        if ((addr = get_s390_panic_task())) {
                                sprintf(readbuf, "\npanic_task: %lx\n", addr);
                                strcat(sendbuf, readbuf);
                        }
                        readbuf[0] = NULLCHAR;
                        get_s390_panicmsg(readbuf);
                        if (strlen(readbuf)) {
                                strcat(sendbuf, "panicmsg: ");
                                strcat(sendbuf, readbuf);
                        }

                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "S390X_DUMP_INIT ")) {
                        
                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* S390X_DUMP_INIT */
                        p2 = strtok(NULL, " ");      /* fd */ 
			p3 = strtok(NULL, " ");      /* filename */

                        mfd = atoi(p2);
                        for (i = 0; i < MAX_REMOTE_FDS; i++) {
                                if (fds[i] == mfd) {
                                        close(mfd);
                                        fds[i] = -1;
                                        break;
                                }
                        }
                        
                        sprintf(sendbuf, "%s %s", savebuf,
                            s390x_dump_init(p3) ? "OK" : "<FAIL>");

                        if ((addr = get_s390x_panic_task())) {
                                sprintf(readbuf, "\npanic_task: %lx\n", addr);
                                strcat(sendbuf, readbuf);
                        }
                        readbuf[0] = NULLCHAR;
                        get_s390x_panicmsg(readbuf);
                        if (strlen(readbuf)) {
                                strcat(sendbuf, "panicmsg: ");
                                strcat(sendbuf, readbuf);
                        }

                        console("[%s]\n", sendbuf);
                        daemon_send(sendbuf, strlen(sendbuf));
                        continue;

                } else if (STRNEQ(recvbuf, "READ_S390D ")) {

                        strcpy(savebuf, recvbuf);

                        p1 = strtok(recvbuf, " ");   /* READ_S390D */
                        p1 = strtok(NULL, " ");      /* filename id */
                        p2 = strtok(NULL, " ");      /* address */
                        p3 = strtok(NULL, " ");      /* length */

                        mfd = atoi(p1);
                        addr = daemon_htol(p2);
                        len = atoi(p3);

                        BZERO(readbuf, READBUFSIZE);
                        errno = 0;

			if ((len = read_s390_dumpfile(UNUSED, 
			    &readbuf[DATA_HDRSIZE], len, UNUSED, addr)) < 0)
                                len = 0;

                        if (len) {
                                sprintf(readbuf, "%s%07ld", DONEMSG,(ulong)len);
                                console("(%ld)\n", (ulong)len);
                        } else {
                                sprintf(readbuf, "%s%07ld", FAILMSG,
                                        (ulong)errno);
                                console("[%s]\n", readbuf);
                        }

                        daemon_send(readbuf, len+DATA_HDRSIZE);
                        continue;

                } else if (STRNEQ(recvbuf, "EXECUTE ")) {

                        strcpy(savebuf, recvbuf);
                        p1 = strtok(recvbuf, " ");   /* EXECUTE */
                        p1 = strtok(NULL, " ");      /* bufsize */
                        p2 = strtok(NULL, " ");      /* MCLXCD or LKCD */
			p3 = strstr(savebuf, p2);
                        bufsize = atol(p1);

			reqsize = bufsize - DATA_HDRSIZE;

			sprintf(readbuf, "echo  | %s", p3);

                        if ((pipe = popen(readbuf, "r")) == NULL) {
                        	BZERO(readbuf, READBUFSIZE);
                                sprintf(readbuf, "%s%07ld", FAILMSG, 
					(ulong)errno);
                                console("[%s]\n", readbuf);
                                daemon_send(readbuf, bufsize);
                                continue;
                        }

			errno = cnt = done = total = first = 0;

        		while (!done) {
                        	BZERO(readbuf, READBUFSIZE);

				cnt = fread(&readbuf[DATA_HDRSIZE], 
					sizeof(char), reqsize, pipe);

				total += cnt;

				if (feof(pipe)) {
					sprintf(readbuf, "%s%07ld", 
						DONEMSG, (ulong)cnt);
					done = TRUE;
				} else if (ferror(pipe)) {
					sprintf(readbuf, "%s%07ld", FAILMSG, 
						(ulong)errno);
					done = TRUE;
				} else
					sprintf(readbuf, "%s%07ld", 
						DATAMSG, (ulong)cnt);

				console("%s[%s]\n", !first++ ? "\n" : "",
					readbuf);
                        	daemon_send(readbuf, bufsize);
			}

			console("EXECUTE total: %ld\n", total);

			pclose(pipe);
			continue;

		} else if (STRNEQ(recvbuf, "EXIT")) {

			sprintf(sendbuf, "%s OK", recvbuf);
			console("[%s]\n", sendbuf);
			daemon_send(sendbuf, strlen(sendbuf));
			return;

		} else {
			sprintf(sendbuf, "%s <FAIL>", recvbuf);
			console("[%s]\n", sendbuf);
			daemon_send(sendbuf, strlen(sendbuf));
		}
	}
}

/*
 *  Common error-checking send routine.
 */
#define MINSENDSIZE  (1448)

static void
daemon_send(void *buffer, int len)
{
        int remaining, count, ret;
        char *bufptr;

        remaining = len;
        bufptr = buffer;
 
        while (remaining) {
                count = MIN(MINSENDSIZE, remaining);

                switch (ret = send(rc->sock, bufptr, count, 0))
		{
		case -1:
			switch (errno)
			{
			case ENOBUFS:
			case ENOMEM:
				sleep(1);
				continue;
			default:
				exit(1);
			}
			break;

		default:
			remaining -= ret;
                	bufptr += ret;
			break;
		}
        }

	console("daemon_send: sent %d\n", len);
}
 
/*
 *  debug print if the -d command line option was used.
 */

int
console(char *fmt, ...)
{
        char output[BUFSIZE*2];
        va_list ap;
        int retval;
	FILE *fp;

        if (!rc->remdebug || !fmt || !strlen(fmt))
                return 0;

        va_start(ap, fmt);
        (void)vsnprintf(output, BUFSIZE*2, fmt, ap);
        va_end(ap);

        if ((fp = fopen(rc->remdebugfile, "a")) == NULL)
                return 0;

        retval = fprintf(fp, "%s", output);
        fclose(fp);

	return retval;
}

/*
 *  Fill in the file size of a freshly opened file.
 */
ulong
daemon_filesize(int fd)
{
	struct stat sbuf;

        if (fstat(fd, &sbuf) == 0)
		return(sbuf.st_size);
        else
                return 0;
}


/*
 *  Check for gdb output stating "(no debugging symbols found)".
 */
char *
no_debugging_symbols_found(char *file)
{
	FILE *pipe;
	char buf[BUFSIZE];

	sprintf(buf, "echo 'q' | /usr/bin/gdb %s", file);
	if ((pipe = popen(buf, "r")) == NULL)
		return "NO_GDB";

	while (fgets(buf, BUFSIZE, pipe)) {
		if (strstr(buf, "(no debugging symbols found)")) {
			pclose(pipe);
			return "NO_DEBUG";
		}
	}
	pclose(pipe);

	return "DEBUG_OK";
}

/*
 *  Read /proc/version into a buffer.
 */
static int
daemon_proc_version(char *buf)
{
        FILE *pipe; 
        struct stat sbuf;
                
        if (stat("/proc/version", &sbuf) == -1)
                return FALSE;

        if ((pipe = popen("/bin/cat /proc/version", "r")) == NULL)
                return FALSE;

        if (fread(buf, sizeof(char),
                BUFSIZE-1, pipe) <= 0) {
		pclose(pipe);
                return FALSE;
	}

        pclose(pipe);

        return TRUE;
}

/*
 *  c/o W. Richard Stevens...
 */

#define OPEN_MAX_GUESS (256)

static int
daemon_init(void)
{
        int i;
        pid_t pid;
        int open_max; 
                
        if ((pid = fork()) < 0)
                return FALSE;
        else if (pid != 0)
                exit(0);

        setsid();
        chdir("/");
        umask(0);

        if ((open_max = sysconf(_SC_OPEN_MAX)) < 0)
                open_max = OPEN_MAX_GUESS;

        for (i = 0; i < open_max; i++)
                close(i);

        signal(SIGCLD, SIG_IGN);

	unsetenv("DISPLAY");

        return TRUE;
}

/*
 *  Determine whether a file is in ELF format by checking the magic number
 *  in the first EI_NIDENT characters of the file.  If it's there, further
 *  qualify it by doing a "file" operation on it.
 */
static int
daemon_is_elf_file(char *s)
{
        int fd, is_elf;
        char magic[EI_NIDENT];
	char buf[BUFSIZE];
	FILE *pipe;

        if ((fd = open(s, O_RDONLY)) < 0) 
                return FALSE;
        
        if (read(fd, magic, EI_NIDENT) != EI_NIDENT) {
                close(fd);
                return FALSE;
        }
        close(fd);

        magic[EI_CLASS] = NULLCHAR;

        if (!STREQ(magic, ELFMAG))
                return FALSE;

	sprintf(buf, "/usr/bin/file -L %s", s);
        if ((pipe = popen(buf, "r")) == NULL) {
        	console("/usr/bin/strings popen failed\n");
                return TRUE;
        } 

	is_elf = FALSE;
        while (fgets(buf, BUFSIZE-1, pipe)) {
                if (strstr(buf, " ELF ") && strstr(buf, "executable")) {
                        is_elf = TRUE;
                        break;
                }
        }
        pclose(pipe);

	return is_elf;
}

/*
 *  Translate ASCII hex addresses.
 */
static ulong
daemon_htol(char *s)
{
    	long i, j; 
	ulong n;

    	if (strlen(s) > MAX_HEXADDR_STRLEN) 
		exit(1);

    	for (n = i = 0; s[i] != 0; i++) {
	        switch (s[i]) 
	        {
	            case 'a':
	            case 'b':
	            case 'c':
	            case 'd':
	            case 'e':
	            case 'f':
	                j = (s[i] - 'a') + 10;
	                break;
	            case 'A':
	            case 'B':
	            case 'C':
	            case 'D':
	            case 'E':
	            case 'F':
	                j = (s[i] - 'A') + 10;
	                break;
	            case '1':
	            case '2':
	            case '3':
	            case '4':
	            case '5':
	            case '6':
	            case '7':
	            case '8':
	            case '9':
	            case '0':
	                j = s[i] - '0';
	                break;
		    case 'x':
		    case 'X':
			continue;
	            default:
			exit(0);
	        }
	        n = (16 * n) + j;
    	}

    	return(n);
}


/*
 *  Adapted from filesys.c, seach the default directories for a kernel
 *  that matches /proc/version.  daemon_build_searchdirs() builds an
 *  array of directory names.
 */

#define CREATE  1
#define DESTROY 0
#define DEFAULT_SEARCHDIRS 4

static int
daemon_find_booted_kernel(char *namelist)
{
	char kernel[BUFSIZE];
	char command[BUFSIZE];
	char buffer[BUFSIZE];
	char proc_version[BUFSIZE];
	char *version;
	char **searchdirs;
	int i;
        DIR *dirp;
        struct dirent *dp;
	FILE *pipe;
	int found;
	struct stat sbuf;

	console("\n");

	if (stat("/proc/version", &sbuf) < 0) {
		console("/proc/version not found\n");
		return FALSE;
	}

	if (!daemon_proc_version(proc_version)) {
                console("cannot read /proc/version\n");
                return FALSE;
	}

	version = proc_version;

        searchdirs = daemon_build_searchdirs(CREATE);

	for (i = 0, found = FALSE; !found && searchdirs[i]; i++) { 
	        dirp = opendir(searchdirs[i]);
		if (!dirp)
			continue;
	        for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
			sprintf(kernel, "%s%s", searchdirs[i], dp->d_name);

			if (daemon_mount_point(kernel) ||
			    !daemon_file_readable(kernel) || 
                            !daemon_is_elf_file(kernel))
				continue;

			sprintf(command, "/usr/bin/strings %s", kernel);
	        	if ((pipe = popen(command, "r")) == NULL) {
				console("/usr/bin/strings popen failed\n");
				continue;
			}

			while (fgets(buffer, BUFSIZE-1, pipe)) {
				if (STREQ(buffer, version)) {
					found = TRUE;
					break;
				}
			}
			pclose(pipe);
	
			if (found)
				break;
	        }
		closedir(dirp);
	}

	daemon_mount_point(DESTROY);
	daemon_build_searchdirs(DESTROY);

	if (found) {
		console("booted kernel: %s\n", kernel);
		strcpy(namelist, kernel);
                return TRUE;
	}

	console("cannot find booted kernel\n");
	return FALSE;
}

static char **
daemon_build_searchdirs(int create)
{
	int i;
	int cnt;
	DIR *dirp;
        struct dirent *dp;
	char dirbuf[BUFSIZE];
	static char **searchdirs = { 0 };
	static char *default_searchdirs[DEFAULT_SEARCHDIRS+1] = {
        	"/usr/src/linux/",
        	"/boot/",
		"/boot/efi/",
        	"/",
        	NULL
	};

	if (!create) {
		if (searchdirs) {
			for (i = DEFAULT_SEARCHDIRS; searchdirs[i]; i++) 
				free(searchdirs[i]);
			free(searchdirs);
		}
		return NULL;
	}

	cnt = DEFAULT_SEARCHDIRS;   

        if ((dirp = opendir("/usr/src"))) {
                for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) 
			cnt++;

		if ((searchdirs = (char **)malloc(cnt * sizeof(char *))) 
		    == NULL) {
			console("/usr/src/ directory list malloc failed: %s\n",
				strerror(errno));
			closedir(dirp);
			return default_searchdirs;
		} 

		for (i = 0; i < DEFAULT_SEARCHDIRS; i++) 
			searchdirs[i] = default_searchdirs[i];
		cnt = DEFAULT_SEARCHDIRS;

		rewinddir(dirp);

        	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
			if (STREQ(dp->d_name, "linux") ||
			    STREQ(dp->d_name, ".") ||
			    STREQ(dp->d_name, ".."))
				continue;

			sprintf(dirbuf, "/usr/src/%s", dp->d_name);
			if (daemon_mount_point(dirbuf))
				continue;
			if (!daemon_is_directory(dirbuf))
				continue;

			if ((searchdirs[cnt] = (char *)
			    malloc(strlen(dirbuf)+2)) == NULL) {
				console("/usr/src/ directory entry malloc failed: %s\n",
					strerror(errno));
				break;
			}
			sprintf(searchdirs[cnt], "%s/", dirbuf); 
			cnt++;
		}
		searchdirs[cnt] = NULL;
		closedir(dirp);
	}

	for (i = 0; searchdirs[i]; i++) 
		console("searchdirs[%d]: %s\n", i, searchdirs[i]);

	return searchdirs;
}

/*
 *  Determine whether a file is a mount point, without the benefit of stat().
 *  This horrendous kludge is necessary to avoid uninterruptible stat() or 
 *  fstat() calls on nfs mount-points where the remote directory is no longer 
 *  available.
 */
static int
daemon_mount_point(char *name)
{
	int i;
	static int mount_points_gathered = -1;
	static char **mount_points;
        char *arglist[MAXARGS];
	char buf[BUFSIZE];
	char cmd[BUFSIZE];
	int argc, found;
	struct stat sbuf;
        FILE *pipe;

	/*
	 *  The first time through, stash a list of mount points.
	 */

	if (mount_points_gathered < 0) {
		found = mount_points_gathered = 0; 

        	if (stat("/proc/mounts", &sbuf) == 0)
			sprintf(cmd, "/bin/cat /proc/mounts");
		else if (stat("/etc/mtab", &sbuf) == 0)
			sprintf(cmd, "/bin/cat /etc/mtab");
		else
                	return FALSE;

        	if ((pipe = popen(cmd, "r")) == NULL)
                	return FALSE;

		while (fgets(buf, BUFSIZE, pipe)) {
        		argc = daemon_parse_line(buf, arglist);
			if (argc < 2)
				continue;
			found++;
		}
		pclose(pipe);

		if (!(mount_points = (char **)malloc(sizeof(char *) * found)))
			return FALSE;

                if ((pipe = popen(cmd, "r")) == NULL) 
                        return FALSE;

		i = 0;
                while (fgets(buf, BUFSIZE, pipe) && 
		       (mount_points_gathered < found)) {
                        argc = daemon_parse_line(buf, arglist);
                        if (argc < 2)
                                continue;
			if ((mount_points[i] = (char *)
			     malloc(strlen(arglist[1])*2))) { 
				strcpy(mount_points[i], arglist[1]);
                        	mount_points_gathered++, i++;
			}
                }
        	pclose(pipe);
	}

	/*
	 *  A null name string means we're done with this routine forever,
	 *  so the malloc'd memory can be freed.
	 */
        if (!name) {   
                for (i = 0; i < mount_points_gathered; i++) 
                        free(mount_points[i]);
                free(mount_points);
                return FALSE;
        }


	for (i = 0; i < mount_points_gathered; i++) {
		if (STREQ(name, mount_points[i]))
			return TRUE;
	}


        return FALSE;
}

/*
 *  Check whether a file is a directory.
 */
static int
daemon_is_directory(char *file)
{
    struct stat sbuf;

    if (!file || !strlen(file))
        return(FALSE);

    if (stat(file, &sbuf) == -1)
        return(FALSE);                         /* This file doesn't exist. */

    return((sbuf.st_mode & S_IFMT) == S_IFDIR ? TRUE : FALSE);
}

/*
 *  Check whether a file is readable.
 */
static int
daemon_file_readable(char *file)
{
        struct stat sbuf;
        long tmp;
        int fd;

        if (stat(file, &sbuf) < 0)
                return FALSE;

        if ((fd = open(file, O_RDONLY)) < 0)
                return FALSE;

        if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
                close(fd);
                return FALSE;
        }
        close(fd);

        return TRUE;
}

/*
 *  Parse a line into tokens, populate the passed-in argv[] array, and return
 *  the count of arguments found.  This function modifies the passed-string 
 *  by inserting a NULL character at the end of each token.  Expressions 
 *  encompassed by parentheses, and strings encompassed by apostrophes, are 
 *  collected into single tokens.
 */
int
daemon_parse_line(char *str, char *argv[])
{
	int i, j;
    	int string;
	int expression;

	for (i = 0; i < MAXARGS; i++)
		argv[i] = NULL;

	daemon_clean_line(str);

        if (str == NULL || strlen(str) == 0)
                return(0);

        i = j = 0;
        string = expression = FALSE;
        argv[j++] = str;

    	while (TRUE) {
		if (j == MAXARGS) {
			console("too many arguments in string!\n");
			return 0;
		}

        	while (str[i] != ' ' && str[i] != '\t' && str[i] != NULLCHAR) {
            		i++;
        	}

	        switch (str[i])
	        {
	        case ' ':
	        case '\t':
	            str[i++] = NULLCHAR;
	
	            if (str[i] == '"') {    
	                str[i] = ' ';
	                string = TRUE;
	                i++;
	            }

                    if (str[i] == '(') {     
                        expression = TRUE;
                    }
	
	            while (str[i] == ' ' || str[i] == '\t') {
	                i++;
	            }
	
	            if (str[i] != NULLCHAR && str[i] != '\n') {
	                argv[j++] = &str[i];
	                if (string) {
	                        string = FALSE;
	                        while (str[i] != '"' && str[i] != NULLCHAR)
	                                i++;
	                        if (str[i] == '"')
	                                str[i] = ' ';
	                }
                        if (expression) {
                                expression = FALSE;
                                while (str[i] != ')' && str[i] != NULLCHAR)
                                        i++;
                        }
	                break;
	            }
	                        /* else fall through */
	        case '\n':
	            str[i] = NULLCHAR;
	                        /* keep falling... */
	        case NULLCHAR:
	            argv[j] = NULLCHAR;
	            return(j);
	        }
    	}  
}

/*
 *  Strip line-beginning and line-ending whitespace and linefeeds.
 */

char *strip_linefeeds(char *line)
{
	return(daemon_clean_line(line));
}

static char *
daemon_clean_line(char *line)
{
        char buf[BUFSIZE];
        char *p;

        if (line == NULL || strlen(line) == 0)
                return(line);

        strcpy(buf, line);
        p = &buf[0];
        while (*p == ' ' || *p == '\t')
                p++;
        strcpy(line, p);

        if (line == NULL || strlen(line) == 0)
                return(line);

        p = &LASTCHAR(line);

        while (*p == '\n')
                *p = NULLCHAR;

        if (line == NULL || strlen(line) == 0)
                return(line);

        p = &LASTCHAR(line);

        while (*p == ' ' || *p == '\t') {
                *p = NULLCHAR;
                if (p == line)
                        break;
                p--;
        }

        return(line);
}


/*
 *  Service not offered by the daemon.
 */

int
monitor_memory(long *a1, long *a2, long *a3, long *a4)
{
	return FALSE;
}

static int
daemon_find_module(char *release, char *filename, char *retbuf)
{
	char dir[BUFSIZE];
	int found;

	found = FALSE;

	sprintf(dir, "%s/%s", DEFAULT_REDHAT_DEBUG_LOCATION, release);
	found = daemon_search_directory_tree(dir, filename, retbuf);

	if (!found) {
        	sprintf(dir, "/lib/modules/%s", release);
		found = daemon_search_directory_tree(dir, filename, retbuf);
	}

	return found;
}


int
daemon_search_directory_tree(char *directory, char *file, char *retbuf)
{
	char command[BUFSIZE];
	char buf[BUFSIZE];
	FILE *pipe;
	int found;

	if (!daemon_file_exists("/usr/bin/find", NULL) || 
	    !daemon_file_exists("/bin/echo", NULL) ||
	    !daemon_is_directory(directory)) 
		return FALSE;

	sprintf(command, 
            "/usr/bin/find %s -name %s -print; /bin/echo search done",
		directory, file);

        if ((pipe = popen(command, "r")) == NULL) 
                return FALSE;

	found = FALSE;

        while (fgets(buf, BUFSIZE-1, pipe) || !found) {
                if (STREQ(buf, "search done\n")) 
                        break;
                
                if (!found &&
                    STREQ((char *)basename(strip_linefeeds(buf)), file)) {
                        strcpy(retbuf, buf);
			found = TRUE;
                }
        }

        pclose(pipe);

	return found;
}

static int
daemon_file_exists(char *file, struct stat *sp)
{
        struct stat sbuf;

        if (stat(file, sp ? sp : &sbuf) == 0)
                return TRUE;

        return FALSE;
}


static int 
daemon_checksum(char *file, long *retsum)
{
        int i; 
        int fd; 
        ssize_t cnt;
        char buf[MIN_PAGE_SIZE];
        long csum; 
                                
        if ((fd = open(file, O_RDONLY)) < 0)
                return FALSE;
   
        csum = 0;
        BZERO(buf, MIN_PAGE_SIZE);
        while ((cnt = read(fd, buf, MIN_PAGE_SIZE)) > 0) {
                for (i = 0; i < cnt; i++)
                        csum += buf[i];
                BZERO(buf, MIN_PAGE_SIZE);
        }
        close(fd);

        *retsum = csum;

        return TRUE;
}

#else

static void copy_to_local_namelist(struct remote_file *);
static char *create_local_namelist(struct remote_file *);
static int remote_find_booted_kernel(struct remote_file *);
static int remote_proc_version(char *);
static int validate_phys_base(physaddr_t, physaddr_t, physaddr_t);
static int remote_file_open(struct remote_file *);
static int remote_file_close(struct remote_file *);
static int identical_namelist(char *, struct remote_file *);
void remote_socket_options(int);
static int copy_remote_file(struct remote_file *, int, char *, char *);
static void copy_remote_gzip_file(struct remote_file *, char *, char *);
static int remote_file_checksum(struct remote_file *);
static int remote_file_type(char *);
static int remote_lkcd_dump_init(void);
static int remote_s390_dump_init(void);
static int remote_netdump_init(void);
static int remote_tcp_read(int, const char *, size_t);
static int remote_tcp_read_string(int, const char *, size_t, int);
static int remote_tcp_write(int, const void *, size_t);
static int remote_tcp_write_string(int, const char *);

struct _remote_context {
        uint flags;
        int n_cpus;
        int vfd;
        char remote_type[10];
} remote_context;

#define NIL_FLAG       (0x01U)

#define NIL_MODE() (rc->flags & NIL_FLAG)

struct _remote_context *rc = &remote_context;

/*
 *  Parse, verify and establish a connection with the network daemon
 *  specified on the crash command line.
 *
 *  The format is: [remote-hostname]:port[,remote-namelist][,remote-dumpfile]
 *
 *  where everything but the port number is optional, and the remote-namelist
 *  and remote-dumpfile can be reversed.
 *
 *    1. The default remote host is the local host. 
 *    2. The default dumpfile is /dev/mem. 
 *    3. If no remote-namelist and remote-dumpfile are given, the daemon
 *       is queried for a kernel that matches the remote /proc/version.
 *       If no local kernel namelist is entered, the remote version will
 *       be copied locally when fd_init() is called.
 *    4. If a remote-dumpfile is given with no remote namelist, it is presumed
 *       that the kernel namelist will be entered locally.
 */

int
is_remote_daemon(char *dp)
{
	char *p1;
	static char defaulthost[MAXHOSTNAMELEN+1];
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *portp, *filep, *file1, *file2;
	struct hostent *hp;
        struct sockaddr_in serv_addr;

	if (!strstr(dp, ":") || file_exists(dp, NULL))
		return FALSE;

	pc->port = 0;
	pc->server = pc->server_memsrc = NULL;
	rc->vfd = pc->rmfd = pc->rkfd = -1;
	file1 = file2 = NULL;

	if ((filep = strstr(dp, ","))) {
		*filep = NULLCHAR;
		filep++;
	}

	if (*dp == ':') {
        	BZERO(defaulthost, MAXHOSTNAMELEN+1);
        	gethostname(defaulthost, MAXHOSTNAMELEN);
        	pc->server = defaulthost;
		portp = dp+1;
	} else {
		pc->server = strtok(dp, ":");	
		portp = strtok(NULL, ":");
	}

	if (portp == NULL) 
		return FALSE;

        if (decimal(portp, 0))
                pc->port = (ushort)atoi(portp);
        else
        	return FALSE;

	if (filep) {
		file1 = strtok(filep, ",");
		file2 = strtok(NULL, ",");
	}

	if (!pc->server || !pc->port) 
		return FALSE;

	if (CRASHDEBUG(1)) {
		fprintf(fp, "server: [%s]\n", pc->server);
		fprintf(fp, "  port: [%d]\n", pc->port);
		fprintf(fp, " file1: [%s]\n", file1);
		fprintf(fp, " file2: [%s]\n", file2);
	}

        if ((hp = gethostbyname(pc->server)) == NULL) {
                herror(pc->server);
                error(FATAL, "gethostbyname [%s] failed\n", pc->server);
        }

	if (CRASHDEBUG(1)) {
		struct in_addr *ip;
        	char **listptr;

        	listptr = hp->h_addr_list;
        	while ((ip = (struct in_addr *) *listptr++) != NULL)
                	printf("%s\n", inet_ntoa(*ip));
	}

        if ((pc->sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                perror("socket");
                error(FATAL, "socket call failed\n");
        }

        BZERO((char *)&serv_addr, sizeof(struct sockaddr_in));
        serv_addr.sin_family = AF_INET;
        BCOPY(hp->h_addr, (char *)&serv_addr.sin_addr, hp->h_length);
        serv_addr.sin_port = htons(pc->port);

        if (connect(pc->sockfd, (struct sockaddr *)&serv_addr,
            sizeof(struct sockaddr_in)) < 0) {
                herror(hp->h_name);
                error(FATAL, "connect [%s:%d] failed\n", hp->h_name, pc->port);
                clean_exit(1);
        }

	if (CRASHDEBUG(1))
        	printf("connect [%s:%d]: success\n", hp->h_name, pc->port);

	remote_socket_options(pc->sockfd);

	/*
	 * Try and use NIL mode.
	 */
	BZERO(sendbuf, BUFSIZE);
	BZERO(recvbuf, BUFSIZE);
	sprintf(sendbuf, "NIL");
	remote_tcp_write_string(pc->sockfd, sendbuf);
	remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, 0);
	if (!strstr(recvbuf, "<FAIL>")) {
		rc->flags |= NIL_FLAG;
		p1 = strtok(recvbuf, " ");  /* NIL */
		p1 = strtok(NULL, " ");     /* remote type */
		if (p1 && p1[0] != 'L')
			pc->flags2 |= REM_PAUSED_F;
	}
        /*
         *  Get the remote machine type and verify a match.  The daemon pid
         *  is also used as a live system initial context.
         */
        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "MACHINE_PID");
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        p1 = strtok(recvbuf, " ");  /* MACHINE */
        p1 = strtok(NULL, " ");     /* machine type */
	if (CRASHDEBUG(1))
        	printf("remote MACHINE: %s\n", p1);
	if (!STREQ(pc->machine_type, p1))
		error(FATAL, "machine type mismatch: local: %s remote: %s\n",
			pc->machine_type, p1);
        p1 = strtok(NULL, " ");     /* pid */
        pc->server_pid = atol(p1);

	if (file1) {
		switch (remote_file_type(file1))
		{
		case TYPE_ELF:
			pc->server_namelist = file1; 
			break;
		case TYPE_NETDUMP:
                        pc->server_memsrc = file1;
                        pc->flags |= REM_NETDUMP;
                        break;
		case TYPE_MCLXCD:
			pc->server_memsrc = file1;
			pc->flags |= REM_MCLXCD;
			break;
		case TYPE_DEVMEM:
			pc->server_memsrc = file1;
			break;
		case TYPE_LKCD:
			pc->server_memsrc = file1;
			pc->flags |= REM_LKCD;
			break;
                case TYPE_S390D:
                        pc->server_memsrc = file1;
                        pc->flags |= REM_S390D;
                        break;
		}
	}

        if (file2) {
                switch (remote_file_type(file2))
                {
                case TYPE_ELF:
                        if (pc->server_namelist)
                                error(FATAL,
                                    "two remote namelists entered: %s and %s\n",
                                         file1, file2);
                        pc->server_namelist = file2;
                        break;
                case TYPE_NETDUMP:
                        if (pc->server_memsrc)
                                error(FATAL,
                                    "neither %s or %s is an ELF file\n",
                                         file1, file2);
                        pc->server_memsrc = file2;
                        pc->flags |= REM_NETDUMP;
                        break;
                case TYPE_MCLXCD:
                        if (pc->server_memsrc)
                                error(FATAL,
                                    "neither %s or %s is an ELF file\n",
                                         file1, file2);
                        pc->server_memsrc = file2;
                        pc->flags |= REM_MCLXCD;
                        break;
		case TYPE_LKCD:
                        if (pc->server_memsrc)
                                error(FATAL,
                                    "neither %s or %s is an ELF file\n",
                                         file1, file2);
                        pc->server_memsrc = file2;
                        pc->flags |= REM_LKCD;
                        break;
                case TYPE_S390D:
                        if (pc->server_memsrc)
                                error(FATAL,
                                    "neither %s or %s is an ELF file\n",
                                         file1, file2);
                        pc->server_memsrc = file2;
                        pc->flags |= REM_S390D;
                        break;
                case TYPE_DEVMEM:
                        pc->server_memsrc = file2;
                        break;
                }

        }

	return TRUE;
}

/*
 *  Determine whether a file is a kernel or a memory source.
 */
static int
remote_file_type(char *file)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "TYPE %s", file);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());

        if (strstr(recvbuf, "<FAIL>"))
                error(FATAL, "invalid remote file name: %s\n", file);
        else if (strstr(recvbuf, " UNSUPPORTED"))
                error(FATAL, "unsupported remote file type: %s\n", file);
	else if (strstr(recvbuf, " NETDUMP"))
		return TYPE_NETDUMP;
        else if (strstr(recvbuf, " ELF")) 
                return TYPE_ELF;        
        else if (strstr(recvbuf, " MCLXCD")) 
		return TYPE_MCLXCD;
        else if (strstr(recvbuf, " DEVMEM")) 
		return TYPE_DEVMEM;
        else if (strstr(recvbuf, " LKCD")) 
		return TYPE_LKCD;
        else if (strstr(recvbuf, " S390D")) 
		return TYPE_S390D;
       
        return (error(FATAL, "unknown remote file type: %s\n", file));
}

/*
 *  Try to set the receive buffer size to READBUFSIZE with setsockopt(), 
 *  storing the value returned by getsockopt() after the attempt is made.
 *  Then enforce a SO_RCVLOWAT (low water mark) of 1, to ensure that error
 *  recovery won't get hung in the recv() call in remote_clear_pipeline().
 */
void
remote_socket_options(int sockfd)
{
	int rcvbuf, optlen;

	pc->rcvbufsize = rcvbuf = READBUFSIZE;

	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf,
	    sizeof(rcvbuf)) < 0) {
		error(INFO, "SO_RCVBUF setsockopt error\n");
		return;
	}

	optlen = sizeof(rcvbuf);
        if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf,
            (socklen_t *)&optlen) < 0) {
                error(INFO, "SO_RCVBUF getsockopt error\n");
                return;
        }

	if (CRASHDEBUG(1))
        	printf("socket SO_RCVBUF size: %d\n", rcvbuf); 

	rcvbuf = 1;

        if (setsockopt(sockfd, SOL_SOCKET, SO_RCVLOWAT, (char *)&rcvbuf,
            sizeof(rcvbuf)) < 0) {
		/*
		 *  Earlier versions of Linux TCP won't accept this option,
                 *  which is hardcoded to the desired count of 1 anyway.
		 *  Set it to 0, and verify it as 1 in the getsockopt() call.
		 */
		if (CRASHDEBUG(1)) 
                	error(INFO, "SO_RCVLOWAT setsockopt error: %s\n",
				strerror(errno));
		rcvbuf = 0;
        }

        optlen = sizeof(rcvbuf);
        if (getsockopt(sockfd, SOL_SOCKET, SO_RCVLOWAT, (char *)&rcvbuf,
            (socklen_t *)&optlen) < 0) {
                error(INFO, "SO_RCVLOWAT getsockopt error\n");
                return;
        }

	if (CRASHDEBUG(1) || (rcvbuf != 1))
        	error(INFO, "socket SO_RCVLOWAT value: %d\n", rcvbuf); 

}

/*
 * Wrapper around recv to read full length packet.
 */
static int
remote_tcp_read(int sock, const char *pv_buffer, size_t cb_buffer)
{
	size_t cb_total = 0;

	do
	{
		ssize_t cb_read = recv(sock, (void*)pv_buffer, cb_buffer, MSG_NOSIGNAL);

		if (cb_read <= 0)
			return cb_read;
		cb_total += cb_read;
		cb_buffer -= cb_read;
		pv_buffer = (char *)pv_buffer + cb_read;
	} while (cb_buffer);

	return cb_total;
}

/*
 * Wrapper around recv to read full string packet.
 */
static int
remote_tcp_read_string(int sock, const char *pv_buffer, size_t cb_buffer, int nil_mode)
{
	size_t cb_total = 0;

	do
	{
		ssize_t cb_read = recv(sock, (void*)pv_buffer, cb_buffer, MSG_NOSIGNAL);

		if (cb_read <= 0)
			return cb_read;
		cb_total += cb_read;
		if (!nil_mode && cb_total >= 4)
			return cb_total;
		if (!pv_buffer[cb_read - 1])
			return cb_total;
		cb_buffer -= cb_read;
		pv_buffer = (char *)pv_buffer + cb_read;
	} while (cb_buffer);

	return cb_total;
}

/*
 * Wrapper around send to send full packet.
 */
static int
remote_tcp_write(int sock, const void *pv_buffer, size_t cb_buffer)
{
	do
	{
		size_t cb_now = cb_buffer;
		ssize_t cb_written = send(sock, (const char *)pv_buffer, cb_now, MSG_NOSIGNAL);

		if (cb_written < 0)
			return 1;
		cb_buffer -= cb_written;
		pv_buffer = (char *)pv_buffer + cb_written;
	} while (cb_buffer);

	return 0;
}

/*
 * Wrapper around tcp_write to send a string
 */
static int
remote_tcp_write_string(int sock, const char *pv_buffer)
{
	return remote_tcp_write(sock, pv_buffer, strlen(pv_buffer) + 1);
}


/*
 *  Request that the daemon open a file.
 */
static int
remote_file_open(struct remote_file *rfp)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *p1;

	BZERO(sendbuf, BUFSIZE);
	BZERO(recvbuf, BUFSIZE);
       	sprintf(sendbuf, "OPEN %s", rfp->filename);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());

        if (CRASHDEBUG(1))
                fprintf(fp, "remote_file_open: [%s]\n", recvbuf);

        if (strstr(recvbuf, "O_RDWR") || strstr(recvbuf, "O_RDONLY")) {
                p1 = strtok(recvbuf, " ");  /* OPEN */
                p1 = strtok(NULL, " ");     /* filename */
                p1 = strtok(NULL, " ");     /* fd */
                rfp->fd = atoi(p1);
                p1 = strtok(NULL, " ");     /* flags */
                if (STREQ(p1, "O_RDWR"))
			rfp->flags |= O_RDWR;
		else if (STREQ(p1, "O_RDONLY"))
			rfp->flags |= O_RDONLY;
                p1 = strtok(NULL, " ");     /* size */
		rfp->size = atoi(p1);
		return TRUE;
        } else 
		return FALSE;
}

/*
 *  Request that the daemon close a previously-opened file.
 */
static int
remote_file_close(struct remote_file *rfp)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];

	BZERO(sendbuf, BUFSIZE);
	BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "CLOSE %d", rfp->fd);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());

	return (strstr(recvbuf, "OK") ? TRUE : FALSE);
}

/*
 *  Get a copy of the daemon machine's /proc/version
 */
static int
remote_proc_version(char *buf)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "PROC_VERSION");
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (STREQ(recvbuf, "<FAIL>")) {
		buf[0] = 0;
                return FALSE;
	}
        strcpy(buf, recvbuf);
	return TRUE;
}

/*
 *  Check that virt_phys_base when accessed via
 *  phys_base - text_start is phys_base.
 */
static int
validate_phys_base(physaddr_t phys_base, physaddr_t text_start, physaddr_t virt_phys_base)
{
        ulong value;

        if (CRASHDEBUG(3))
                fprintf(fp, "validate_phys_base: virt_phys_base=0x%llx phys_base=0x%llx text_start=0x%llx calc=0x%llx\n",
                        (long long unsigned int)virt_phys_base,
			(long long unsigned int)phys_base,
			(long long unsigned int)text_start,
			(long long unsigned int)virt_phys_base + phys_base - text_start);

        if (READMEM(pc->rmfd, (void*)&value, sizeof(value),
		    virt_phys_base, virt_phys_base + phys_base - text_start)
	    == sizeof(value)) {
                if (value == phys_base)
                        return 1;
        }
        return 0;
}

/*
 *  Get remote phys_base based on virtual address of "phys_base".
 */
physaddr_t
get_remote_phys_base(physaddr_t text_start, physaddr_t virt_phys_base)
{
        int vcpu;
        ulong value;

	if (rc->vfd < 0) {
		struct remote_file remote_file, *rfp;

		rfp = &remote_file;
		BZERO(rfp, sizeof(struct remote_file));
		rfp->filename = "/dev/vmem";
		if (remote_file_open(rfp)) {
			rc->vfd = rfp->fd;
		} else
			return 0;
	}

        for (vcpu = 0; vcpu < rc->n_cpus; vcpu++)
                if (remote_memory_read(rc->vfd, (void*)&value, sizeof(value),
				       virt_phys_base, vcpu) == sizeof(value)) {
                        if (validate_phys_base(value, text_start, virt_phys_base))
                                return value;
                }

        return 0;
}

/*
 *  Do a remote VTOP if supported.
 */
physaddr_t
remote_vtop(int cpu, physaddr_t virt_addr)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *p1;
	int errflag;
	ulong value;

	if (!rc->remote_type[0])
		return 0;       /* Not a special remote. */

	BZERO(sendbuf, BUFSIZE);
	BZERO(recvbuf, BUFSIZE);
	sprintf(sendbuf, "VTOP %d %llx", cpu, (long long unsigned int)virt_addr);
	remote_tcp_write_string(pc->sockfd, sendbuf);
	remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());

	if (CRASHDEBUG(2))
		fprintf(fp, "remote_vtop: [%s]\n", recvbuf);

	if (strstr(recvbuf, "<FAIL>"))
		error(FATAL, "remote_vtop for CPU %d\n", cpu);
	p1 = strtok(recvbuf, " ");  /* VTOP */
	p1 = strtok(NULL, " ");     /* cpu */
	p1 = strtok(NULL, " ");     /* vaddr */
	p1 = strtok(NULL, " ");     /* paddr */

	errflag = 0;
	value = htol(p1, RETURN_ON_ERROR|QUIET, &errflag);
	if (!errflag) {
		return value;
	}
	return 0;
}

/*
 *  Get a copy of the daemon machine cpu regs.
 */
int
get_remote_regs(struct bt_info *bt, ulong *eip, ulong *esp)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *p1, *p2;
	int errflag;
	ulong value;

	if (!rc->remote_type[0])
		return 0;       /* Not a special remote. */

	*eip = 0;
	*esp = 0;

	BZERO(sendbuf, BUFSIZE);
	BZERO(recvbuf, BUFSIZE);
	sprintf(sendbuf, "FETCH_LIVE_IP_SP_BP %d", bt->tc->processor);
	if (remote_tcp_write_string(pc->sockfd, sendbuf))
		return 0;
	errflag = remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
	if (errflag <= 0)
		return 0;

	if (CRASHDEBUG(1))
		fprintf(fp, "get_remote_regs(cpu=%d): [%s]\n",
			bt->tc->processor, recvbuf);

	if (strstr(recvbuf, "<FAIL>")) {
		error(INFO, "get_remote_regs for CPU %d\n", bt->tc->processor);
		return 0;
	}
	p1 = strtok(recvbuf, " ");  /* FETCH_LIVE_IP_SP_BP */
	p1 = strtok(NULL, " ");     /* cpu */
	p1 = strtok(NULL, ":");     /* cs */
	p1 = strtok(NULL, " ");     /* ip */
	p2 = strtok(NULL, ":");     /* ss */
	p2 = strtok(NULL, " ");     /* sp */
	/* p2 = strtok(NULL, " ");     bp */

	errflag = 0;
	value = htol(p1, RETURN_ON_ERROR|QUIET, &errflag);
	if (!errflag) {
		*eip = value;
	}

	errflag = 0;
	value = htol(p2, RETURN_ON_ERROR|QUIET, &errflag);
	if (!errflag) {
		*esp = value;
	}
	return 1;
}

/*
 *  Get a remote cr3 if supported.
 */
physaddr_t
get_remote_cr3(int cpu)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *p1;
	int errflag;
	ulong value;

	if (!rc->remote_type[0])
		return 0;       /* Not a special remote. */

	BZERO(sendbuf, BUFSIZE);
	BZERO(recvbuf, BUFSIZE);
	sprintf(sendbuf, "FETCH_LIVE_CR3 %d", cpu);
	if (remote_tcp_write_string(pc->sockfd, sendbuf))
		return 0;
	remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());

	if (CRASHDEBUG(1))
		fprintf(fp, "get_remote_cr3: [%s]\n", recvbuf);

	if (strstr(recvbuf, "<FAIL>"))
		error(FATAL, "get_remote_cr3 for CPU %d\n", cpu);
	p1 = strtok(recvbuf, " ");  /* FETCH_LIVE_CR3 */
	p1 = strtok(NULL, " ");     /* cpu */
	p1 = strtok(NULL, " ");     /* cr3 */

	errflag = 0;
	value = htol(p1, RETURN_ON_ERROR|QUIET, &errflag);
	if (!errflag)
		return value;
	return 0;
}


/*
 *
 *   Set up the file descriptors and file name strings if they haven't
 *   been set up before:
 *
 *   1. pc->namelist must be set to a local kernel namelist, which will be 
 *      copied from the remote machine if it was not specified.
 *
 *   2. pc->dumpfile will never be set for a remote operation, because there
 *      is no difference to readmem().
 *
 *   3. pc->server_namelist may be set if it has to be copied across.
 *
 *   4. pc->server_memsrc will be set to either /dev/mem or the dumpfile.
 */
void
remote_fd_init(void)
{
	char filename[BUFSIZE];
	struct remote_file remote_file, *rfp;

	rfp = &remote_file;

	if (pc->namelist && pc->server_namelist) {
		error(INFO, "too many namelists\n");
		program_usage(SHORT_FORM);
	}

	if ((pc->namelist || pc->server_namelist) &&
             pc->namelist_debug && pc->system_map) {
                        error(INFO,
              "too many namelist options:\n       %s\n       %s\n       %s\n",
                                pc->namelist ? 
				pc->namelist : pc->server_namelist, 
				pc->namelist_debug,
                                pc->system_map);
		program_usage(SHORT_FORM);
	}

	/*
	 *  Account for the remote possibility of a local dumpfile 
	 *  being entered on the command line.
	 */
        if (pc->flags & MEMORY_SOURCES) {
		if (pc->server_memsrc) {
                	error(INFO, "too many dumpfile/memory arguments\n");
			program_usage(SHORT_FORM);
		}
		pc->flags2 |= MEMSRC_LOCAL;
		if (pc->flags & (DEVMEM|MEMMOD)) {
			if (!get_proc_version())
                        	error(INFO, "/proc/version: %s\n", 
					strerror(errno));
        		pc->flags |= LIVE_SYSTEM;  
		}
	} else {
        	/*
         	 *  First open the remote memory source, defaulting to /dev/mem
         	 *  if no remote dumpfile name was entered.  If it is /dev/mem,
         	 *  then also go get the remote /proc/version.
         	 */
		pc->readmem = read_daemon;

		if (!pc->server_memsrc) 
			pc->server_memsrc = "/dev/mem";
	
		if (STREQ(pc->server_memsrc, "/dev/mem"))
			pc->flags |= REM_LIVE_SYSTEM;
	
		BZERO(rfp, sizeof(struct remote_file));
	       	rfp->filename = pc->server_memsrc;

	       	if (remote_file_open(rfp)) {
	               	pc->rmfd = rfp->fd;
	               	if (rfp->flags & O_RDWR)
	                       	pc->flags |= MFD_RDWR;

	               	if (BITS32() && REMOTE_ACTIVE()) {
	                       	BZERO(rfp, sizeof(struct remote_file));
	                       	rfp->filename = "/dev/kmem";
	                       	if (remote_file_open(rfp))
	                               	pc->rkfd = rfp->fd;
	               	}

                        if ((pc->flags & REM_NETDUMP) &&
                            !remote_netdump_init())
                                error(FATAL,
                                    "%s: remote initialization failed\n",
                                        pc->server_memsrc);

			if ((pc->flags & REM_LKCD) &&
			    !remote_lkcd_dump_init())
                               	error(FATAL, 
				    "%s: remote initialization failed\n",
                                       	pc->server_memsrc);

                        if ((pc->flags & REM_S390D) &&
                            !remote_s390_dump_init())
                                error(FATAL,
                                    "%s: remote initialization failed\n",
                                        pc->server_memsrc);

			if (REMOTE_DUMPFILE())
				pc->writemem = write_daemon;

	       	} else
	               	error(FATAL, "cannot open remote memory source: %s\n",
	                       	pc->server_memsrc);
	
	       	if (REMOTE_ACTIVE() && !remote_proc_version(kt->proc_version))
	       		error(WARNING, 
				"daemon cannot access /proc/version\n\n");
	}

	/*
	 *  If a local namelist was entered, check whether it's readable.  
         *  If a server namelist was entered, copy it across.
         *  If no server namelist was entered, query the daemon for it,
         *  and if found, copy it across,
         */
	if (pc->namelist) {
        	if ((pc->nfd = open(pc->namelist, O_RDONLY)) < 0)
                	error(FATAL, "%s: %s\n", pc->namelist, strerror(errno));
               	close(pc->nfd);
               	pc->nfd = -1;
		pc->flags |= NAMELIST_LOCAL;
	} else if (pc->server_namelist) {
        	BZERO(rfp, sizeof(struct remote_file));
        	rfp->filename = pc->server_namelist;
		if (!remote_file_open(rfp)) {
			error(FATAL, "daemon cannot open: %s\n",
				pc->server_namelist);
		}
		copy_to_local_namelist(rfp);
		remote_file_close(rfp);
	} else {
        	BZERO(rfp, sizeof(struct remote_file));
		BZERO(filename, BUFSIZE);
		rfp->filename = filename;
		if (!remote_find_booted_kernel(rfp)) 
			error(FATAL, 
			    "remote daemon cannot find booted kernel\n");
		if (!remote_file_open(rfp))
			error(FATAL, "remote daemon cannot open: %s\n",
				pc->server_namelist);
		copy_to_local_namelist(rfp);
		remote_file_close(rfp);
	}

	if (REMOTE_ACTIVE()) 
        	pc->flags |= LIVE_SYSTEM;  
}

/*
 *  Copy a remote kernel to a local file, which gets unlinked in the normal
 *  course of events.  However, the pc->nfd file descriptor will be kept 
 *  alive in case there's a command put in place to keep the file around.
 */
static void
copy_to_local_namelist(struct remote_file *rfp)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char readbuf[READBUFSIZE];
	int tty;

	if (pc->flags & KERNEL_DEBUG_QUERY) {
		/*
		 *  Don't bother copying the kernel if the daemon can
		 *  figure it out.
		 */
        	BZERO(sendbuf, BUFSIZE);
        	BZERO(recvbuf, BUFSIZE);
        	sprintf(sendbuf, "DEBUGGING_SYMBOLS %s", rfp->filename);
		remote_tcp_write_string(pc->sockfd, sendbuf);
		remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
		if (strstr(recvbuf, "NO_DEBUG")) {
			sprintf(readbuf, "%s@%s", rfp->filename, pc->server);
			pc->namelist = readbuf;
			no_debugging_data(FATAL);
		}
	}

	pc->namelist = create_local_namelist(rfp);

	if (pc->flags & NAMELIST_LOCAL)
		return;

	if ((pc->nfd = open(pc->namelist, 
	     O_RDWR|O_CREAT|O_TRUNC, S_IRWXU)) < 0) {
		pc->flags &= ~UNLINK_NAMELIST;
		error(FATAL, "cannot create local copy of kernel (%s)\n",
			pc->namelist);
	}

	tty = !(pc->flags & SILENT) && isatty(fileno(stdin));

	if (!(pc->flags & NAMELIST_NO_GZIP)) {
		copy_remote_gzip_file(rfp, pc->namelist, tty ?
		    "please wait... (copying remote kernel namelist: " : NULL);
        	if (tty) 
			fprintf(stderr, 
            "\r                                                           \r");
		return;
	}

        if (copy_remote_file(rfp, pc->nfd, pc->namelist,
	    tty ?  "please wait... (copying remote kernel namelist: " : NULL)) {
        	if (tty) 
			fprintf(stderr, 
            "\r                                                           \r");
        } else 
		error(FATAL, "write to local copy of kernel namelist failed\n");
}

/*
 *  Try to create a file of the format: vmlinux@@hostname
 *  If it already exists, append "_0", "_1", etc. until one's not found.
 *
 *  The file will be unlinked by display_sys_stats() the first time it's
 *  called.
 */

static char *
create_local_namelist(struct remote_file *rfp)
{
	char buf[BUFSIZE];
	char *p1;
	int i, use_local_copy;

	p1 = (char *)basename(rfp->filename);

	sprintf(buf, "%s@%s", p1, pc->server);
	for (i = 0, use_local_copy = FALSE; i >= 0; i++) {
		if (file_exists(buf, NULL)) {
			if (identical_namelist(buf, rfp)) {
				use_local_copy = TRUE;
				break;
			}
			sprintf(buf, "%s@%s_%d", p1,pc->server, i);
		} else
			break;
	}

	if ((p1 = (char *)malloc((size_t)(strlen(buf)+1))) == NULL) 
		error(FATAL, "cannot malloc temporary file name buffer\n");

	strcpy(p1, buf);

	if (use_local_copy) 
		pc->flags |= NAMELIST_LOCAL;
	else
		pc->flags |= UNLINK_NAMELIST;

	return p1;
}

/*
 *  Before copying a kernel across, check whether a kernel of the same
 *  name is identical to the remote version.
 */
static int
identical_namelist(char *file, struct remote_file *rfp)
{
	char *vers;
	FILE *pipe;
	struct stat sbuf;
	long csum;
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char readbuf[BUFSIZE*2];

	if (stat(file, &sbuf) < 0)
		return FALSE;

	if (sbuf.st_size != rfp->size) 
		return FALSE;

	if (remote_file_checksum(rfp) && file_checksum(file, &csum) &&
	    (csum == rfp->csum))
		return TRUE;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        BZERO(readbuf, BUFSIZE);

        sprintf(sendbuf, "LINUX_VERSION %s", rfp->filename);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (strstr(recvbuf, "<FAIL>")) 
		return FALSE;

        vers = recvbuf;

        sprintf(readbuf, "/usr/bin/strings %s | grep 'Linux version'", 
		file);
        if ((pipe = popen(readbuf, "r"))) {
        	BZERO(readbuf, BUFSIZE);
                if (fread(readbuf, sizeof(char), BUFSIZE-1, pipe) <= 0) {
			pclose(pipe);
			return FALSE;
		}
                pclose(pipe);
	} else
		return FALSE;

	if (CRASHDEBUG(1)) {
		fprintf(fp, "remote version: [%s]\n", vers);
		fprintf(fp, "local version: [%s]\n", readbuf);
		fprintf(fp, "%s vs. %s => %s\n",
			file, rfp->filename,
			STREQ(vers, readbuf) ? "IDENTICAL" : "DIFFERENT");
	}

	return (STREQ(vers, readbuf));
}

/*
 *  If a remote file exists, get its checksum and return TRUE.
 */
static int
remote_file_checksum(struct remote_file *rfp)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *p1;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "SUM %s", rfp->filename);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (strstr(recvbuf, "<FAIL>")) {
                error(INFO, "%s: does not exist on server %s\n",
                        rfp->filename, pc->server);
                return FALSE;
        }
        strtok(recvbuf, " ");         /* SUM */
        p1 = strtok(NULL, " ");       /* filename */
        p1 = strtok(NULL, " ");       /* checksum */

        rfp->csum = htol(p1, FAULT_ON_ERROR, NULL);
	return TRUE;
}

/*
 *  Copy a remote file locally, distinguishing it by appending an ampersand
 *  and the server name.
 *
 *  If the kernel is requested, save the unlinked copy of the remote kernel
 *  in a local file, using the same name created by create_local_namelist(). 
 *
 *  If a dumpfile, module, or any other file for that matter, append an
 *  ampersand plus the server name.
 *
 *  Other files may have their local filename altered if a file of the
 *  same name exists with a different checksum.
 */
int
get_remote_file(struct remote_file *rfp)
{
	int i;
	char local[BUFSIZE];
	char readbuf[READBUFSIZE];
	char *p1;
	struct load_module *lm;
	int cnt, sfd, err, retval;
	long csum;

	if (!REMOTE()) {
		error(INFO, "no remote files in use\n");
		return FALSE;
	}

	if (rfp->local)
		goto generic_file_save;

	sprintf(readbuf, "%s@%s", pc->server_memsrc, pc->server);
	if (STREQ(rfp->filename, "dumpfile") || 
	    STREQ(rfp->filename, pc->server_memsrc) ||
	    STREQ(rfp->filename, basename(pc->server_memsrc)) ||
	    STREQ(rfp->filename, readbuf)) 
		goto dumpfile_save;

	sprintf(readbuf, "%s", pc->namelist);
	if ((p1 = strstr(readbuf, "@")))
		*p1 = NULLCHAR;
	if (STREQ(rfp->filename, "kernel") ||
	    STREQ(rfp->filename, pc->namelist) ||
	    STREQ(rfp->filename, pc->server_namelist) ||
	    STREQ(rfp->filename, readbuf))
		goto kernel_save;


	if (STREQ(rfp->filename, "modules")) {
        	for (i = 0; i < kt->mods_installed; i++) {
                	lm = &st->load_modules[i];
                	if (lm->mod_flags & MOD_REMOTE) {
				fprintf(fp, "%s module saved as: %s\n",
					lm->mod_name, lm->mod_namelist);
				lm->mod_flags &= ~MOD_REMOTE;
			}
        	}
		return TRUE;
	}

	if (is_module_name(rfp->filename, NULL, &lm)) {
                if (lm->mod_flags & MOD_REMOTE) {
                        fprintf(fp, "%s module saved as: %s\n",
                               lm->mod_name, lm->mod_namelist);
			lm->mod_flags &= ~MOD_REMOTE;
                }
		return TRUE;
	}

	strcpy(local, rfp->filename);
	if ((p1 = strstr(local, ".o"))) {
		*p1 = NULLCHAR;
		if (is_module_name(basename(local), NULL, &lm)) {
                	if (lm->mod_flags & MOD_REMOTE) {
                        	fprintf(fp, "%s module saved as: %s\n",
                               		lm->mod_name, lm->mod_namelist);
				lm->mod_flags &= ~MOD_REMOTE;
				return TRUE;
			}
		}
	}

generic_file_save:

	cnt = 0;
	sprintf(local, "%s@%s", basename(rfp->filename), pc->server);
        while (file_exists(local, NULL)) {
		if (CRASHDEBUG(1))
                	fprintf(fp, "%s already exists in this directory\n",
                        	local);
		if (file_checksum(local, &csum) && (csum == rfp->csum)) {
			if (CRASHDEBUG(1))
				error(NOTE, 
			    	    "local %s checksum matches -- using it\n",
					local);
			strcpy(rfp->local, local);
			return TRUE;
		}
		sprintf(local, "%s@%s_%d", 
			basename(rfp->filename), pc->server, ++cnt);
	}

        if (!remote_file_open(rfp)) {
                error(INFO, "daemon cannot open: %s\n", rfp->filename);
		return FALSE;
	}

        if ((sfd = open(local, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU)) < 0) {
                error(INFO, "open: %s: %s\n", local, strerror(errno));
                remote_file_close(rfp);
                return FALSE;
        }

        if (copy_remote_file(rfp, sfd, local, rfp->flags & REMOTE_VERBOSE ?
	    "please wait... (copying remote file: " : NULL)) {
		if (rfp->flags & REMOTE_VERBOSE)
                	fprintf(stderr,
             "\rremote file saved as: \"%s\"                                \n",
                        local);
		retval = TRUE;
		rfp->flags |= REMOTE_COPY_DONE;
        } else {
                fprintf(stderr,
                "\r%s NOT saved                                             \n",
                        rfp->filename);
		retval = FALSE;
        }

        close(sfd);
        remote_file_close(rfp);

	if (cnt)
		strcpy(rfp->local, local);

	return retval;

kernel_save: 

	if (pc->flags & NAMELIST_SAVED) {
		error(INFO, "\"%s\" is already saved\n", pc->namelist);
		return FALSE;
	}

	if (pc->flags & NAMELIST_LOCAL) {
		error(INFO, "\"%s\" is a local file\n", pc->namelist);
		return FALSE;
	}

	if ((sfd = open(pc->namelist, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU)) < 0) {
		error(INFO, "open: %s: %s\n", pc->namelist, strerror(errno));
		return FALSE;
	}

	err = 0;
        lseek(sfd, 0, SEEK_SET);
        lseek(pc->nfd, 0, SEEK_SET);

	while ((cnt = read(pc->nfd, readbuf, READBUFSIZE)) > 0) {
		if (write(sfd, readbuf, cnt) != cnt) {
			error(INFO, "write:%s: %s\n", 
				pc->namelist, strerror(errno));
			err++;
			break;
		}
	}

	close(sfd);

	if (err) {
		fprintf(fp, "%s NOT saved\n", pc->namelist);
		unlink(pc->namelist);
		retval = FALSE;
	} else {
		fprintf(fp, "kernel saved as: \"%s\"\n", pc->namelist);
		close(pc->nfd);
		pc->nfd = -1;
		pc->flags |= NAMELIST_SAVED;
		retval = TRUE;
	}

	return (retval);

dumpfile_save:

        if (pc->flags & DUMPFILE_SAVED) {
                error(INFO, "\"%s@%s\" is already saved\n", 
			basename(pc->server_memsrc), pc->server);
		return FALSE;
	}

        if (pc->flags2 & MEMSRC_LOCAL) {
                error(INFO, "%s is a local file\n", pc->dumpfile);
		return FALSE;
	}

	if (!(REMOTE_DUMPFILE())) {
                error(INFO, "%s is not a dumpfile\n", pc->server_memsrc);
		return FALSE;
	}

	sprintf(local, "%s@%s", basename(pc->server_memsrc), pc->server);

	if (file_exists(local, NULL)) {
		error(INFO, "%s already exists in this directory\n",
			local);
		return FALSE;
	}

        rfp->filename = pc->server_memsrc;
        if (!remote_file_open(rfp)) {
                error(INFO, "daemon cannot open: %s\n", pc->server_memsrc);
		return FALSE;
	}

        if ((sfd = open(local, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU)) < 0) {
                error(INFO, "open: %s: %s\n", local, strerror(errno));
		remote_file_close(rfp);
                return FALSE;
        }

	if (copy_remote_file(rfp, sfd, local, 
	    "please wait... (copying remote dumpfile: ")) {
                fprintf(stderr, 
		"\rdumpfile saved as: \"%s\"                                \n",
			local);
                pc->flags |= DUMPFILE_SAVED;
		retval = TRUE;
	} else {
                fprintf(stderr, 
		"\r%s NOT saved                                             \n",
			pc->server_memsrc);
		retval = FALSE;
	}
	
	close(sfd);
        remote_file_close(rfp);

	return (retval);

}

/*
 *  Query the remote daemon for the kernel name that is running.
 */
static int 
remote_find_booted_kernel(struct remote_file *rfp)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char *p1;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "FIND_BOOTED_KERNEL");
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        strtok(recvbuf, " ");           /* FIND_BOOTED_KERNEL */
        p1 = strtok(NULL, " ");         /* filename */
        if (STREQ(p1, "<FAIL>"))
		return FALSE;
	strcpy(rfp->filename, p1);

	return TRUE;
}

static int
remote_lkcd_dump_init(void)
{
        char sendbuf[BUFSIZE];
        char recvbuf[BUFSIZE];
        char *p1, *p2, *p3;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "LKCD_DUMP_INIT %d %s", pc->rmfd, pc->server_memsrc);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (strstr(recvbuf, "<FAIL>"))
                return FALSE;

	p1 = strstr(recvbuf, "panic_task: "); 
	p2 = strstr(recvbuf, "panicmsg: "); 

	if (p1) {
		p1 += strlen("panic_task: ");
		p3 = strstr(p1, "\n");
		*p3 = NULLCHAR;
		tt->panic_task = htol(p1, FAULT_ON_ERROR, NULL);
		if (CRASHDEBUG(1))
			fprintf(fp, "panic_task: %lx\n", tt->panic_task);
	}
	if (p2) {
		p2 += strlen("panicmsg: ");
		if (CRASHDEBUG(1))
			fprintf(fp, "panicmsg: %s", p2);
	}

	set_remote_lkcd_panic_data(tt->panic_task, p2);

        return TRUE;
}


static int
remote_s390_dump_init(void)
{
        char sendbuf[BUFSIZE];
        char recvbuf[BUFSIZE];
        char *p1, *p2, *p3;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "S390_DUMP_INIT %d %s", pc->rmfd, pc->server_memsrc);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (strstr(recvbuf, "<FAIL>"))
                return FALSE;

	p1 = strstr(recvbuf, "panic_task: "); 
	p2 = strstr(recvbuf, "panicmsg: "); 

	if (p1) {
		p1 += strlen("panic_task: ");
		p3 = strstr(p1, "\n");
		*p3 = NULLCHAR;
		tt->panic_task = htol(p1, FAULT_ON_ERROR, NULL);
		if (CRASHDEBUG(1))
			fprintf(fp, "panic_task: %lx\n", tt->panic_task);
	}
	if (p2) {
		p2 += strlen("panicmsg: ");
		if (CRASHDEBUG(1))
			fprintf(fp, "panicmsg: %s", p2);
	}

        return TRUE;
}

static int
remote_netdump_init(void)
{
        char sendbuf[BUFSIZE];
        char recvbuf[BUFSIZE];
        char *p1, *p2;
	ulong panic_task;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "NETDUMP_INIT %d %s", pc->rmfd, pc->server_memsrc);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (strstr(recvbuf, "<FAIL>"))
                return FALSE;

        p1 = strstr(recvbuf, "panic_task: ");

        if (p1) {
                p1 += strlen("panic_task: ");
                p2 = strstr(p1, "\n");
                *p2 = NULLCHAR;
                panic_task = htol(p1, FAULT_ON_ERROR, NULL);
		tt->panic_task = panic_task;  /*  kludge */
                if (CRASHDEBUG(1))
                        fprintf(fp, "panic_task: %lx\n", tt->panic_task);
        }

        return TRUE;
}

uint
remote_page_size(void)
{
        char sendbuf[BUFSIZE];
        char recvbuf[BUFSIZE];
        char *p1, *p2, *p3;
	uint psz;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);

	if (REMOTE_ACTIVE())
        	sprintf(sendbuf, "PAGESIZE LIVE");
	else if (REMOTE_PAUSED())
		sprintf(sendbuf, "PAGESIZE NIL");
	else if (pc->flags & REM_NETDUMP)
        	sprintf(sendbuf, "PAGESIZE NETDUMP");
	else if (pc->flags & REM_MCLXCD)
        	sprintf(sendbuf, "PAGESIZE MCLXCD");
	else if (pc->flags & REM_LKCD)
        	sprintf(sendbuf, "PAGESIZE LKCD");
        else if (pc->flags & REM_S390D)
                sprintf(sendbuf, "PAGESIZE S390D");
	else 
                error(FATAL, 
		 "cannot determine remote page size (unknown memory source)\n");

        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        if (strstr(recvbuf, "FAIL"))
                error(FATAL, "cannot determine remote page size\n");
        strtok(recvbuf, " ");           /* PAGESIZE */
        p1 = strtok(NULL, " ");         /* LIVE, MCLXCD or LKCD */
        p1 = strtok(NULL, " ");         /* page size */
        p2 = strtok(NULL, " ");         /* remote type */
        p3 = strtok(NULL, " ");         /* number of Cpus */
	psz = atoi(p1);

	if (psz > MAXRECVBUFSIZE)
		error(FATAL, 
		   "remote page size %d is larger than MAXRECVBUFSIZE!\n", psz);

        if (p2) {
                strncpy(rc->remote_type, p2, sizeof(rc->remote_type) - 1);
                rc->remote_type[sizeof(rc->remote_type) - 1] = 0;
        }
        if (p3)
                rc->n_cpus = atoi(p3);

	return psz;
}

/*
 *  Copy a remote file to a local file, closing the passed-in fd when done.
 *  A running tally of percentage-done numbers can  optionally be displayed.
 */
static int
copy_remote_file(struct remote_file *rfp, int fd, char *file, char *ttystr)
{
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE*2];
	char readbuf[READBUFSIZE];
	char *bufptr;
	long pct, last;
        ulong size, offset, filesize;
        ulong ret, req, tot;
	int sysret ATTRIBUTE_UNUSED;
	ssize_t bytes ATTRIBUTE_UNUSED;

	last = -1;
	lseek(fd, 0, SEEK_SET);
	filesize = rfp->size;

	for (offset = 0; offset < filesize; ) {

		size = MIN(filesize-offset, pc->rcvbufsize); 
		
		BZERO(sendbuf, BUFSIZE);
        	sprintf(sendbuf, "READ %d %lx %ld", rfp->fd, offset, size);
		bytes = write(pc->sockfd, sendbuf, strlen(sendbuf) + 1);

        	bzero(readbuf, READBUFSIZE);

        	req = size;
        	tot = 0;

        	sprintf(recvbuf, "%s:FAIL", sendbuf);
        	bufptr = readbuf;
        	while (req) {
                	ret = recv(pc->sockfd, bufptr, req, 0);
                	if (!tot && STRNEQ(bufptr, recvbuf)) {
                        	tot = -1;
                        	break;
                	}
                	req -= ret;
                	tot += ret;
                	bufptr += ret;
        	}

		if (tot == -1)
			break;

		if (write(fd, readbuf, size) != size) {
			error(INFO, 
			    "%swrite to local file \"%s\" failed", 
				ttystr ? "\n" : "", file);
			close(fd);
			return FALSE;
		}

		offset += tot;

		if (ttystr) {
			pct = (offset*100)/filesize;  

			if (pct > last) {         /* readline work-around... */
				if (last < 0) 
					sprintf(readbuf, "echo -n \'%s0%%)\'", 
						ttystr);
				else if (last >= 0 && last < 10) 
                                    	sprintf(readbuf,
                                	    "echo -e -n \"\\b\\b\\b%ld%%)\"", 
						pct);
				else if (last < 100) 
                                    	sprintf(readbuf,
                                	    "echo -e -n \"\\b\\b\\b\\b%ld%%)\"",
					        pct);
                               	sysret = system(readbuf);
				last = pct;
			}
		}
	}

	if (offset != filesize) {
		error(INFO, "%swrite to local file \"%s\" failed", 
			ttystr ? "\n" : "", file);
		close(fd);
		return FALSE;
	}

	fsync(fd);

	return TRUE;
}


/*
 *  Copy a remote file to a local file, closing the passed-in fd when done.
 *  A running tally of percentage-done numbers can  optionally be displayed.
 */

static void 
copy_remote_gzip_file(struct remote_file *rfp, char *file, char *ttystr)
{
        int done;
        char sendbuf[BUFSIZE];
        char readbuf[READBUFSIZE];
	char gziphdr[DATA_HDRSIZE];
        char *bufptr, *p1;
	FILE *pipe;
	size_t gtot;
	struct stat sbuf;
        ulong pct, ret, req, tot, total;

	sprintf(readbuf, "/usr/bin/gunzip > %s", pc->namelist);
        if ((pipe = popen(readbuf, "w")) == NULL)
		error(FATAL, "cannot open pipe to create %s\n", pc->namelist);

        BZERO(sendbuf, BUFSIZE);
        sprintf(sendbuf, "READ_GZIP %ld %s", pc->rcvbufsize, rfp->filename);
        remote_tcp_write_string(pc->sockfd, sendbuf);

       	bzero(readbuf, READBUFSIZE);

	done = total = 0;
	gtot = 0;

	while (!done) {

		req = pc->rcvbufsize; 
		bufptr = readbuf;
		tot = 0;

                while (req) {
                        ret = (ulong)recv(pc->sockfd, bufptr, req, 0); 
                        if (!tot) {
				if (STRNEQ(bufptr, FAILMSG)) {
					fprintf(fp, 
					    "copy_remote_gzip_file: %s\n",
						bufptr);
                                	tot = -1;
                                	break;
				}
				if (STRNEQ(bufptr, DONEMSG) ||
				    STRNEQ(bufptr, DATAMSG)) {
					BCOPY(bufptr, gziphdr, DATA_HDRSIZE);
					if (CRASHDEBUG(1))
						fprintf(fp, 
				                "copy_remote_gzip_file: [%s]\n",
							gziphdr);
                        		p1 = strtok(gziphdr, " "); /* DONE */
					if (STREQ(p1, "DONE"))
						done = TRUE;
                        		p1 = strtok(NULL, " ");     /* count */
					gtot = atol(p1);
					total += gtot;
				}
                        } 
                        req -= ret;
                        tot += ret;
                        bufptr += ret;
                }

                if (tot == -1)
                        break;

		if (fwrite(&readbuf[DATA_HDRSIZE], sizeof(char), gtot, pipe) 
		    != gtot) 
			error(FATAL, "fwrite to %s failed\n", pc->namelist);

		if (ttystr && (stat(pc->namelist, &sbuf) == 0)) {
			pct = (sbuf.st_size * 100)/rfp->size;
			fprintf(stderr, "\r%s%ld%%)%s", 
				ttystr, pct, CRASHDEBUG(1) ? "\n" : "");
		}
	}

	if (CRASHDEBUG(1))
		fprintf(fp, "copy_remote_gzip_file: GZIP total: %ld\n", total);

	pclose(pipe);
}

/*
 *  Set up to have get_remote_file() copy the remote module locally.
 *  If it's already here, no copy is done.
 */
int
find_remote_module_objfile(struct load_module *lm, char *module, char *retbuf)
{
	int absolute;
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	char local[BUFSIZE];
	char found[BUFSIZE];
	char *p1;
	long csum;
	struct remote_file remote_file, *rfp;

	rfp = &remote_file;
	BZERO(rfp, sizeof(struct remote_file));

	absolute = (*module == '/');

	if (absolute) {
		if ((p1 = strstr(module, "@"))) {
			*p1 = NULLCHAR;
		} else {
			error(FATAL, 
		      "module file name must have \"@server-name\" attached\n");
		}

		sprintf(local, "%s@%s", basename(module), pc->server);
		rfp->filename = module;
		rfp->local = local;

		if (!remote_file_checksum(rfp)) {
                	error(INFO, "%s: does not exist on server %s\n", 
				module, pc->server);
                	return FALSE;
        	}
	} else { 
		if ((p1 = strstr(module, "@")))
			*p1 = NULLCHAR;
	
		sprintf(local, "%s@%s", module, pc->server);
	
	        BZERO(sendbuf, BUFSIZE);
	        BZERO(recvbuf, BUFSIZE);
	        sprintf(sendbuf, "FIND_MODULE %s %s",
	                kt->utsname.release, module);
	        remote_tcp_write_string(pc->sockfd, sendbuf);
	        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
	        if (strstr(recvbuf, "<FAIL>")) {
			fprintf(fp, "find_remote_module_objfile: [%s]\n", 
				recvbuf);
	                return FALSE;
		}
	        strtok(recvbuf, " ");               /* FIND_MODULE */
	        p1 = strtok(NULL, " ");             /* release */
	        p1 = strtok(NULL, " ");             /* module */
	        strcpy(found, strtok(NULL, " "));   /* resultant path */
	        p1 = strtok(NULL, " ");             /* checksum */
		csum = htol(p1, FAULT_ON_ERROR, NULL);
	
		rfp->filename = found;
		rfp->local = local;
		rfp->csum = csum;
	}

	if (get_remote_file(rfp)) {
                if (!is_elf_file(rfp->local)) {
                	error(INFO, 
                        	"%s@%s: not an ELF format object file\n",
                                        rfp->filename, pc->server);
			return FALSE;
                }
                strcpy(retbuf, rfp->local);
		if (rfp->flags & REMOTE_COPY_DONE) {
			lm->mod_flags |= MOD_REMOTE;
			pc->flags |= UNLINK_MODULES;
		}
                return TRUE;
        }

	return FALSE;
}

/*
 *  Tell the daemon to free the current dumpfile memory.
 */
int 
remote_free_memory(void)
{
        char sendbuf[BUFSIZE];
        char recvbuf[BUFSIZE];
        char *type, *p1;

	if (pc->flags & REM_NETDUMP)
		type = "NETDUMP";
	else if (pc->flags & REM_MCLXCD)
		type = "MCLXCD";
	else if (pc->flags & REM_LKCD)
		type = "LKCD";
        else if (pc->flags & REM_S390D)
                type = "S390D";
	else
		return 0;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "MEMORY FREE %s", type);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        p1 = strtok(recvbuf, " ");      /* MEMORY */
        p1 = strtok(NULL, " ");         /* FREE */
        p1 = strtok(NULL, " ");         /* MCLXCD, LKCD etc. */
        p1 = strtok(NULL, " ");         /* pages */
        if (STREQ(p1, "<FAIL>"))
                return 0;

        return(atol(p1));
}

/*
 *  Return the number of dumpfile pages used by the daemon.
 */
int
remote_memory_used(void)
{
        char sendbuf[BUFSIZE];
        char recvbuf[BUFSIZE];
        char *type, *p1;

        if (pc->flags & REM_NETDUMP)
                type = "NETDUMP";
        else if (pc->flags & REM_MCLXCD)
                type = "MCLXCD";
        else if (pc->flags & REM_LKCD)
                type = "LKCD";
        else if (pc->flags & REM_S390D)
                type = "S390D";
        else
                return 0;

        BZERO(sendbuf, BUFSIZE);
        BZERO(recvbuf, BUFSIZE);
        sprintf(sendbuf, "MEMORY USED %s", type);
        remote_tcp_write_string(pc->sockfd, sendbuf);
        remote_tcp_read_string(pc->sockfd, recvbuf, BUFSIZE-1, NIL_MODE());
        p1 = strtok(recvbuf, " ");          /* MEMORY */
        p1 = strtok(NULL, " ");             /* FREE */
        p1 = strtok(NULL, " ");             /* MCLXCD, LKCD, etc. */
        p1 = strtok(NULL, " ");             /* pages */
        if (STREQ(p1, "<FAIL>"))
                return 0;

        return(atol(p1));
}

/*
 *  Have the daemon return the output of vas_memory_dump(), lkcd_memory_dump().
 *  or dump_lkcd_environment()
 */
int 
remote_memory_dump(int verbose)
{
        char sendbuf[BUFSIZE];
        char readbuf[READBUFSIZE];
	char datahdr[DATA_HDRSIZE];
        char *type, *bufptr, *p1;
	ulong done, total;
	ulong ret, req, tot;
	size_t dtot;

        if (pc->flags & REM_NETDUMP)
                type = "NETDUMP";
        else if (pc->flags & REM_MCLXCD)
                type = "MCLXCD";
        else if (pc->flags & REM_LKCD)
                type = "LKCD";
        else if (pc->flags & REM_S390D)
                type = "S390D";
        else
                return 0;

        BZERO(sendbuf, BUFSIZE);
        sprintf(sendbuf, "MEMORY_DUMP %ld %s%s", pc->rcvbufsize, type,
		verbose ? "_VERBOSE" : "");
        remote_tcp_write_string(pc->sockfd, sendbuf);

       	bzero(readbuf, READBUFSIZE);
	done = total = 0;
	dtot = 0;

	while (!done) {

		req = pc->rcvbufsize;
		bufptr = readbuf;
		tot = 0;

                while (req) {
                        ret = recv(pc->sockfd, bufptr, req, 0); 
                        if (!tot) {
				if (STRNEQ(bufptr, FAILMSG)) {
					fprintf(fp, 
					    "remote_memory_dump: %s\n",
						bufptr);
                                	tot = -1;
                                	break;
				}
				if (STRNEQ(bufptr, DONEMSG) ||
				    STRNEQ(bufptr, DATAMSG)) {
					BCOPY(bufptr, datahdr, DATA_HDRSIZE);
					if (CRASHDEBUG(1))
						fprintf(fp, 
					        "remote_memory_dump: [%s]\n",
							datahdr);
                        		p1 = strtok(datahdr, " "); /* DONE */
					if (STREQ(p1, "DONE"))
						done = TRUE;
                        		p1 = strtok(NULL, " ");     /* count */
					dtot = atol(p1);
					total += dtot;
				}
                        }
                        req -= ret;
                        tot += ret;
                        bufptr += ret;
                }

                if (tot == -1)
                        break;

		if (fwrite(&readbuf[DATA_HDRSIZE], sizeof(char), dtot, fp) 
		    != dtot) 
			error(FATAL, "fwrite to %s failed\n", pc->namelist);
	}

	return 1;
}

/*
 *  Read memory from the remote memory source.  The remote file descriptor
 *  is abstracted to allow for a common /dev/mem-/dev/kmem call.  Since
 *  this is only called from read_daemon(), the request can never exceed
 *  a page in length.
 */
int 
remote_memory_read(int rfd, char *buffer, int cnt, physaddr_t address, int vcpu)
{
        char sendbuf[BUFSIZE];
	char datahdr[DATA_HDRSIZE];
	char *p1;
	int ret, tot;
	ulong addr;

	addr = (ulong)address;  /* may be virtual */

        BZERO(sendbuf, BUFSIZE);
        if (pc->flags & REM_NETDUMP) {
                sprintf(sendbuf, "READ_NETDUMP %lx %d", addr, cnt);
        } else if (pc->flags & REM_MCLXCD)
                sprintf(sendbuf, "READ_MCLXCD %lx %d", addr, cnt);
        else if (pc->flags & REM_LKCD)
                sprintf(sendbuf, "READ_LKCD %d %lx %d", rfd, addr, cnt);
        else if (pc->flags & REM_S390D)
                sprintf(sendbuf, "READ_S390D %d %lx %d", rfd, addr, cnt);
        else if (vcpu >= 0)
                sprintf(sendbuf, "READ_LIVE %d %lx %d %d", rfd, addr, cnt, vcpu);
        else
                sprintf(sendbuf, "READ_LIVE %d %lx %d", rfd, addr, cnt);

	if (remote_tcp_write_string(pc->sockfd, sendbuf))
		return -1;

	/*
	 *  Read request will come back with a singular header 
	 *  followed by the data.
         */
        BZERO(datahdr, DATA_HDRSIZE);
	ret = remote_tcp_read_string(pc->sockfd, datahdr, DATA_HDRSIZE, 1);
	if (ret <= 0)
		return -1;
	if (CRASHDEBUG(3))
		fprintf(fp, "remote_memory_read: [%s]\n", datahdr);
	if (STRNEQ(datahdr, FAILMSG)) {
		p1 = strtok(datahdr, " ");  /* FAIL  */
		p1 = strtok(NULL, " ");     /* errno */
		errno = atoi(p1);
		return -1;
	}

	if (!STRNEQ(datahdr, DONEMSG) && !STRNEQ(datahdr, DATAMSG)) {
		error(INFO, "out of sync with remote memory source\n");
		return -1;
	}

	p1 = strtok(datahdr, " ");  /* DONE */
	p1 = strtok(NULL, " ");     /* count */
	tot = atol(p1);

	if (cnt != tot) {
		error(FATAL,
		      "requested %d bytes remote memory return %d bytes\n",
		      cnt, tot);
		return -1;
	}

	ret = remote_tcp_read(pc->sockfd, buffer, tot);
	if (ret != tot) {
		error(FATAL,
		      "requested %d bytes remote memory return %d bytes\n",
		      ret, tot);
		return -1;
	}
	return tot;
}

/*
 *  If a command was interrupted locally, there may be leftover data waiting
 *  to be read.
 */
void
remote_clear_pipeline(void)
{
	int ret;
	fd_set rfds;
	char recvbuf[READBUFSIZE];
	struct timeval tv;

        tv.tv_sec = 0;
        tv.tv_usec = 0;

        FD_ZERO(&rfds);
        FD_SET(pc->sockfd, &rfds);
        ret = select(pc->sockfd+1, &rfds, NULL, NULL, &tv);

	if (FD_ISSET(pc->sockfd, &rfds)) {
        	ret = recv(pc->sockfd, recvbuf, pc->rcvbufsize, 0); 
		if (CRASHDEBUG(1))
			error(INFO, 
	                    "remote_clear_pipeline(%d): %d bytes discarded\n", 
				pc->sockfd, ret);
	}
}

/*
 *  Attempt to run the user-entered command on the remote system.
 */
int
remote_execute(void)
{
	char command[BUFSIZE];
        char sendbuf[BUFSIZE*2];
        char readbuf[READBUFSIZE];
	char datahdr[DATA_HDRSIZE];
        char *bufptr, *p1;
	ulong done, total;
	ulong ret, req, tot;
	size_t dtot;

	if (!STRNEQ(args[0], "@") || strlen(args[0]) == 1)
		return FALSE;

	shift_string_left(concat_args(command, 0, FALSE), 1);

	if (QUOTED_STRING(command)) 
		strip_ending_char(strip_beginning_char(command, '"'), '"');

	if (CRASHDEBUG(1))
		error(INFO, "remote command: %s\n", command);

        BZERO(sendbuf, BUFSIZE);
        sprintf(sendbuf, "EXECUTE %ld %s", pc->rcvbufsize, command);
        remote_tcp_write_string(pc->sockfd, sendbuf);

       	bzero(readbuf, READBUFSIZE);
	done = total = 0;
	dtot = 0;

	while (!done) {

		req = pc->rcvbufsize;
		bufptr = readbuf;
		tot = 0;

                while (req) {
                        ret = recv(pc->sockfd, bufptr, req, 0); 
                        if (!tot) {
				if (STRNEQ(bufptr, FAILMSG)) {
					fprintf(fp, 
					    "remote_execute: %s\n",
						bufptr);
                                	tot = -1;
                                	break;
				}
				if (STRNEQ(bufptr, DONEMSG) ||
				    STRNEQ(bufptr, DATAMSG)) {
					BCOPY(bufptr, datahdr, DATA_HDRSIZE);
					if (CRASHDEBUG(1))
						fprintf(fp, 
					        "remote_execute: [%s]\n",
							datahdr);
                        		p1 = strtok(datahdr, " "); /* DONE */
					if (STREQ(p1, "DONE"))
						done = TRUE;
                        		p1 = strtok(NULL, " ");     /* count */
					dtot = atol(p1);
					total += dtot;
				}
                        }
                        req -= ret;
                        tot += ret;
                        bufptr += ret;
                }

                if (tot == -1)
                        break;

		if (fwrite(&readbuf[DATA_HDRSIZE], sizeof(char), dtot, fp) 
		    != dtot) 
			error(FATAL, "fwrite failed\n");
	}

	return TRUE;
}

/*
 *  Clean up on exit.
 */
void
remote_exit(void)
{
        char buf[BUFSIZE];

	if (pc->flags & UNLINK_NAMELIST)
        	unlink(pc->namelist);

        if (pc->flags & UNLINK_MODULES)
                unlink_module(NULL);

        BZERO(buf, BUFSIZE);
        sprintf(buf, "EXIT");
        remote_tcp_write_string(pc->sockfd, buf);
	/* 
	 *  Read but ignore the return status -- we don't really care... 
	 */
        remote_tcp_read_string(pc->sockfd, buf, BUFSIZE-1, NIL_MODE());

}
#endif /* !DAEMON */
