/*
 * dsock.c - SCO Unix socket processing functions for lsof
 */


/*
 * Copyright 1995 Purdue Research Foundation, West Lafayette, Indiana
 * 47907.  All rights reserved.
 *
 * Written by Victor A. Abell
 *
 * This software is not subject to any license of the American Telephone
 * and Telegraph Company or the Regents of the University of California.
 *
 * Permission is granted to anyone to use this software for any purpose on
 * any computer system, and to alter it and redistribute it freely, subject
 * to the following restrictions:
 *
 * 1. Neither the authors nor Purdue University are responsible for any
 *    consequences of the use of this software.
 *
 * 2. The origin of this software must not be misrepresented, either by
 *    explicit claim or by omission.  Credit to the authors and Purdue
 *    University must appear in documentation and sources.
 *
 * 3. Altered versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 4. This notice may not be removed or altered.
 */

#ifndef lint
static char copyright[] =
"@(#) Copyright 1995 Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: dsock.c,v 1.7 96/01/11 12:55:24 abe Exp $";
#endif


#include "lsof.h"


/*
 * process_socket() - process socket
 */

void
process_socket(i)
	struct inode *i;		/* inode pointer */
{
	char *cp, *cp1;
	struct domain d;
	char dev_ch[32];
	int fam, j, k;
	struct in_addr *ia;
	int p;
	struct inpcb pcb;
	short pcbs, shs, udpsf, udpsl, udptm;
	struct socket s;
	KA_T sa, spa;
	struct stdata sd;
	struct queue sh;
	struct udpdev udp;

#if	_SCOV<500
	struct sockaddr_in *si;
#else	/* _SCOV>=500 */
	struct sockaddr_in si;
	struct un_dev ud;
#endif	/* _SCOV<500 */
	
	(void) strcpy(Lf->type, "sock");
	pcbs = shs = 0;
/*
 * Read socket.
 */
	if (Socktab == (KA_T)NULL) {
		(void) enter_nm("No kernel socket table");
		return;
	}
	spa = Socktab + (minor(i->i_rdev) * sizeof(struct socket *));
	if (kread(spa, (char *)&sa, sizeof(sa))) {
		(void) sprintf(Namech, "can't read socket pointer at %#x",
			spa);
		enter_nm(Namech);
	}
	if (kread(sa, (char *)&s, sizeof(s))) {
		(void) sprintf(Namech, "can't read socket structure at %#x",
			sa);
		enter_nm(Namech);
		return;
	}
/*
 * Read domain structure.
 */
	if (s.so_proto.pr_domain == (struct domain *)NULL
	||  kread((KA_T)s.so_proto.pr_domain, (char *)&d, sizeof(d))) {
	    (void) sprintf(Namech, "can't read protocol domain from %#x",
		s.so_proto.pr_domain);
	    enter_nm(Namech);
	    return;
	}
/*
 * Process by protocol domain.
 */
	switch ((fam = d.dom_family)) {
	case AF_INET:
	    if (Fnet)
		Lf->sf |= SELNET;
	    (void) strcpy(Lf->type, "inet");
	    printiproto((int)s.so_proto.pr_protocol);
	    Lf->inp_ty = 2;
	/*
	 * Get protocol control block address from stream head queue structure.
	 */
	    if (s.so_stp
	    &&  readstdata((struct stdata *)s.so_stp, &sd) == 0
	    &&  readsthead(sd.sd_wrq, &sh) == 0)
		shs = 1;
	    if (shs && sh.q_ptr) {
		(void) sprintf(dev_ch, "0x%08x", sh.q_ptr);
		enter_dev_ch(dev_ch);
		if (kread((KA_T)sh.q_ptr, (char *)&pcb, sizeof(pcb)) == 0)
		    pcbs = 1;
	    }
	/*
	 * Print local and remote addresses.
	 */
	    if (pcbs) {
		if (pcb.inp_ppcb && strcasecmp(Lf->iproto, "udp") == 0) {

		/*
		 * If this is a UDP socket file, get the udpdev structure
		 * at the PCB's per-protocol control block address.  It
		 * may contain a foreign address.
		 */
		    if (!kread((KA_T)pcb.inp_ppcb, (char *)&udp, sizeof(udp))) {

#if	_SCOV>=500
			if (udp.ud_lsin.sin_addr.s_addr != INADDR_ANY ||
			    udp.ud_lsin.sin_port != 0
			)
			    udpsl = 1;
			else
			    udpsl = 0;
#endif	/* _SCOV>=500 */

			if (udp.ud_fsin.sin_addr.s_addr != INADDR_ANY ||
			    udp.ud_fsin.sin_port != 0
			)
			    udpsf = 1;
			else
			    udpsf = 0;
		    }
		} else
		    udpsf = udpsl = 0;
	    /*
	     * Print the local address from the PCB.  If there is none, and if
	     * this is a 5.0.0 or greater UDP stream, and if it has a local
	     * address set, use it.
	     */
		ia = &pcb.inp_laddr;
		p = (int)ntohs(pcb.inp_lport);

#if     _SCOV>=500
		if (ia->s_addr == INADDR_ANY && p == 0 && udpsl) {
		    ia = &udp.ud_lsin.sin_addr;
		    p = (int)ntohs(udp.ud_lsin.sin_port);
		}

#endif  /* _SCOV>=500 */

		printinaddr(ia, p);
	    /*
	     * Use the PCB's foreign address if it is set.  If not, and if this
	     * is a UDP socket file, use the udpdev structure's foreign address
	     * if it's set.
	     */
		if (pcb.inp_faddr.s_addr != INADDR_ANY || pcb.inp_fport != 0) {
		    ia = &pcb.inp_faddr;
		    p = (int)ntohs(pcb.inp_fport);
		    udptm = 0;
		} else if (udpsf) {
		    ia = &udp.ud_fsin.sin_addr;
		    p = (int)ntohs(udp.ud_fsin.sin_port);
		    udptm = 1;
		} else
		    ia = NULL;
		if (ia) {
		    cp = endnm();
		    if ((MAXPATHLEN - 1 - (cp - Namech)) >= 2)
			(void) strcat(cp, "->");
		    printinaddr(ia, p);
		    if (udptm) {
			if ((cp1 = udp_tm(udp.ud_ftime, &j)) != NULL) {
			    cp = endnm();
			    k = MAXPATHLEN - 1 - (cp - Namech);
			    if (k < j)
				j = k;
			    (void) strncpy(cp, cp1, j);
			    *(cp + j) = '\0';
			}
		    }
		}
	    } else {

#if	_SCOV<500
		if ((si = (struct sockaddr_in *)&s.so_name))
		    printinaddr(&si->sin_addr, (int)ntohs(si->sin_port));
		if ((si = (struct sockaddr_in *)&s.so_peer)) {
		    if (si->sin_addr.s_addr != INADDR_ANY
		    ||  si->sin_port != 0)
		    {
			cp = endnm();
			if ((MAXPATHLEN - 1 - (cp - Namech)) >= 2)
			    (void) strcat(cp, "->");
			printinaddr(&si->sin_addr, (int)ntohs(si->sin_port));
		    }
		}
#else	/* _SCOV>=500 */
		if (s.so_name
		&&  kread((KA_T)s.so_name, (char *)&si, sizeof(si)) == 0)
		    printinaddr(&si.sin_addr, (int)ntohs(si.sin_port));
		if (s.so_peer
		&&  kread((KA_T)s.so_peer, (char *)&si, sizeof(si)) == 0) {
		    if (si.sin_addr.s_addr != INADDR_ANY || si.sin_port != 0) {
			cp = endnm();
			if ((MAXPATHLEN - 1 - (cp - Namech)) >= 2)
			    (void) strcat(cp, "->");
			printinaddr(&si.sin_addr, (int)ntohs(si.sin_port));
		    }
		}
#endif	/* _SCOV<500 */

	    }
	/*
	 * Set size or offset.
	 */
	    if (shs) {
		Lf->sz = (unsigned long)sh.q_count;
		Lf->sz_def = 1;
	    } else
		Lf->off_def = 1;
	    break;

#if	_SCOV>=500
	case AF_UNIX:
	    if (Funix)
		Lf->sf |= SELUNX;
	    (void) strcpy(Lf->type, "unix");
	/*
	 * Read Unix protocol control block and the Unix address structure.
	 */

	    (void) sprintf(dev_ch, "0x%08x", sa);
	    enter_dev_ch(dev_ch);
	    Lf->off_def = 1;
	    if (s.so_stp
	    &&  readstdata((struct stdata *)s.so_stp, &sd) == 0
	    &&  readsthead(sd.sd_wrq, &sh) == 0) {
		if (sh.q_ptr == NULL
		||  kread((KA_T)sh.q_ptr, (char *)&ud, sizeof(ud)))
		{
		    (void) sprintf(Namech, "can't read un_dev from %#x",
			sh.q_ptr);
		    break;
		}
		if (ud.so_rq) {
		    (void) sprintf(dev_ch, "0x%08x", ud.so_rq);
		    enter_dev_ch(dev_ch);
		}
		if (ud.local_addr.sun_family == AF_UNIX) {
		    Lf->inode = (unsigned long)ud.bnd_param.user_addr.inode_no;
		    Lf->inp_ty = 1;
		    ud.local_addr.sun_path[sizeof(ud.local_addr.sun_path) - 1]
			= '\0';
		    if (Sfile && is_file_named(ud.local_addr.sun_path, 0))
			Lf->sf |= SELNM;
		    else
			(void) strcpy(Namech, ud.local_addr.sun_path);
		} else if (ud.for_addr.sun_family == AF_UNIX) {
		    Lf->inode = (unsigned long)ud.bnd_param.user_addr.inode_no;
		    Lf->inp_ty = 1;
		    ud.for_addr.sun_path[sizeof(ud.for_addr.sun_path) - 1]
			= '\0';
		    if (Sfile && is_file_named(ud.for_addr.sun_path, 0))
			Lf->sf |= SELNM;
		    else
			(void) strcpy(Namech, ud.for_addr.sun_path);
		} else if (ud.other_q)
		    (void) sprintf(Namech, "->0x%08x", ud.other_q);
	    } else
		(void) strcpy(Namech, "can't get un_dev");
	    break;
#endif	/* _SCOV>=500 */

	default:
	    printunkaf(fam);
	}
	enter_nm(Namech);
}


/*
 * udp_tm() - compute time since UDP packet was last sent
 */

char *
udp_tm(tm, len)
	time_t tm;			/* time when packet was sent */
	int *len;			/* return length value */
{
	static char buf[16], *cp;
	time_t et, lbolt;
	short hr, min, sec;
/*
 * Read the lightning bolt timer and compute the elapsed time.
 * No elapsed time is returned if:
 *	the global clock frequency variable, Hz, is negative;
 *	the lightning bolt timer is unavailable;
 *	the lightning bolt time is less than the UDP send time;
 *	the elapsed time is zero.
 */
	if (Hz < 0 
	||  Nl[X_LBOLT].n_value == (long)NULL
	||  kread((KA_T)Nl[X_LBOLT].n_value, (char *)&lbolt, sizeof(lbolt))
	||  tm >= lbolt
	||  (et = (time_t)((lbolt - tm) / Hz)) == 0) {
		*len = 0;
		return(NULL);
	}
/*
 * If the time is 100 hours or greater, return the elapsed time as seconds.
 */
	if (et >= (100 * 60 * 60)) {
		(void) sprintf(buf, " %lds ago", et);
		*len = strlen(buf);
		return(buf);
	}
/*
 * Convert seconds to hours, minutes and seconds.
 */
	hr = (short)(et / (60 * 60));
	et %= (60 * 60);
	min = (short)(et / 60);
	sec = (short)(et % 60);
	cp = buf;
	*cp++ = ' ';
/*
 * Format the elapsed time and attach single character suffixes to represent
 * the units: `h' = hours; `m' = minutes; and `s' = seconds:
 */
	if (hr) {
		(void) sprintf(cp, "%dh", hr);
		cp += 2 + ((hr > 9) ? 1 : 0);
	}
	if (min) {
		(void) sprintf(cp, "%dm", min);
		cp += 2 + ((min > 9) ? 1 : 0);
	}
	if (sec) {
		(void) sprintf(cp, "%ds", sec);
		cp += 2 + ((sec > 9) ? 1 : 0);
	}
/*
 * Add the `` ago'' trailer.  Return the string's address and length.
 */
	(void) strcpy(cp, " ago");
	*len = cp - buf + 4;
	return(buf);
}
