/* source: fdname.c */
/* Copyright Gerhard Rieger 2003-2008 */
/* Published under the GNU General Public License V.2, see file COPYING */

/* the subroutine sockname prints the basic info about the address of a socket
   NOTE: it works on UNIX (kernel) file descriptors, not on libc files! */

#include "config.h"
#include "xioconfig.h"	/* what features are enabled */

#include "sysincludes.h"

#include "mytypes.h"
#include "compat.h"
#include "error.h"
#include "sycls.h"
#include "sysutils.h"

#include "filan.h"


struct sockopt {
   int so;
   char *name;
};


int statname(const char *file, int fd, int filetype, FILE *outfile);
int cdevname(int fd, FILE *outfile);
int sockname(int fd, FILE *outfile);
int unixame(int fd, FILE *outfile);
int tcpname(int fd, FILE *outfile);


int fdname(const char *file, int fd, FILE *outfile, const char *numform) {
   struct stat buf = {0};
   int filetype;
   Debug1("checking file descriptor %u", fd);
   if (fd >= 0) {
      if (Fstat(fd, &buf) < 0) {
	 if (errno == EBADF) {
	    Debug2("fstat(%d): %s", fd, strerror(errno));
	    return -1;
	 } else {
	    Error2("fstat(%d): %s", fd, strerror(errno));
	 }
      }
      filetype = (buf.st_mode&S_IFMT)>>12;
      if (numform != NULL) {
	 fprintf(outfile, numform, fd);
      }
      return statname(file, fd, filetype, outfile);
   } else {
      if (Stat(file, &buf) < 0) {
	 Error2("stat(\"%s\"): %s", file, strerror(errno));
      }
      filetype = (buf.st_mode&S_IFMT)>>12;
      return statname(file, -1, filetype, outfile);
   }
}

#if HAVE_PROC_DIR_FD
static int procgetfdname(int fd, char *filepath, size_t pathsize) {
   static pid_t pid = -1;
   char procpath[PATH_MAX];
   int len;

   /* even if configure has shown that we have /proc, we must check if it
      exists at runtime, because we might be in a chroot environment */
#if HAVE_STAT64
   {
      struct stat64 buf;
      if (Stat64("/proc", &buf) < 0) {
	 return -1;
      }
      if (!S_ISDIR(buf.st_mode)) {
	 return -1;
      }
   }
#else /* !HAVE_STAT64 */
   {
      struct stat buf;
      if (Stat("/proc", &buf) < 0) {
	 return -1;
      }
      if (!S_ISDIR(buf.st_mode)) {
	 return -1;
      }
   }
#endif /* !HAVE_STAT64 */
       
   if (pid < 0)  pid = Getpid();
   snprintf(procpath, sizeof(procpath), "/proc/"F_pid"/fd/%d", pid, fd);
   if ((len = Readlink(procpath, filepath, pathsize-1)) < 0) {
      Error4("readlink(\"%s\", %p, "F_Zu"): %s",
	     procpath, filepath, pathsize, strerror(errno));
      return -1;
   }
   filepath[len] = '\0';
   return 0;
}
#endif /* HAVE_PROC_DIR_FD */
   
int statname(const char *file, int fd, int filetype, FILE *outfile) {
   char filepath[PATH_MAX];
   int result;

   filepath[0] = '\0';
#if HAVE_PROC_DIR_FD
   if (fd >= 0) {
      procgetfdname(fd, filepath, sizeof(filepath));
      if (filepath[0] == '/') {
	 file = filepath;
      }
   }
#endif /*  HAVE_PROC_DIR_FD */
   /* now see for type specific infos */
   switch (filetype) {
   case (S_IFIFO>>12):	/* 1, FIFO */
      fputs("pipe", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFCHR>>12):	/* 2, character device */
      if (cdevname(fd, outfile) == 0) {
	 if (file) fprintf(outfile, " %s", file);
      }
      break;
   case (S_IFDIR>>12):	/* 4, directory */
      fputs("dir", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFBLK>>12):	/* 6, block device */
      fputs("blkdev", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFREG>>12):	/* 8, regular file */
      fputs("file", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFLNK>>12):	/* 10, symbolic link */
      fputs("link", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFSOCK>>12): /* 12, socket */
#if WITH_SOCKET
      if (fd >= 0) {
	 result = sockname(fd, outfile);
      } else if (file) {
	 fprintf(outfile, "socket %s", file);
      } else {
	 fputs("socket", outfile);
      }
#else
      Error("SOCKET support not compiled in");
      return -1;
#endif /* !WITH_SOCKET */
      break;
   }
   /* ioctl() */
   fputc('\n', outfile);

   return 0;
}


/* character device analysis */
/* return -1 on error, 0 if no name was found, or 1 if it printed ttyname */
int cdevname(int fd, FILE *outfile) {
   int ret;

   if ((ret = Isatty(fd)) < 0) {
      Error2("isatty(%d): %s", fd, strerror(errno));
      return -1;
   }
   if (ret > 0) {
      char *name;

      fputs("tty", outfile);
      if ((name = Ttyname(fd)) != NULL) {
	 fputc(' ', outfile);
	 fputs(name, outfile);
	 return 1;
      }
   } else {
      fputs("chrdev", outfile);
   }
   return 0;
}


#if WITH_SOCKET
int sockname(int fd, FILE *outfile) {
#define FDNAME_OPTLEN 256
#define FDNAME_NAMELEN 256
   socklen_t optlen;
   int opttype;
#ifdef SO_ACCEPTCONN
   int optacceptconn;
#endif
   int result /*0, i*/;
   char namebuff[FDNAME_NAMELEN];
   char peerbuff[FDNAME_NAMELEN];
   /* in Linux these optcodes are 'enum', but on AIX they are bits! */
   union sockaddr_union sockname, peername;	/* the longest I know of */
   socklen_t namelen;
#if 0 && defined(SIOCGIFNAME)
   /*Linux struct ifreq ifc = {{{ 0 }}};*/
   struct ifreq ifc = {{ 0 }};
#endif

   optlen = FDNAME_OPTLEN;

   Getsockopt(fd, SOL_SOCKET, SO_TYPE,       &opttype,       &optlen);
#ifdef SO_ACCEPTCONN
   Getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &optacceptconn, &optlen);
#endif

   namelen = sizeof(sockname);
   result = Getsockname(fd, &sockname.soa, &namelen);
   if (result < 0) {
      Error2("getsockname(%d): %s", fd, strerror(errno));
      return -1;
   }

   namelen = sizeof(peername);
   result = Getpeername(fd, (struct sockaddr *)&peername, &namelen);
   if (result < 0) {
      Error2("getpeername(%d): %s", fd, strerror(errno));
   }

   switch (sockname.soa.sa_family) {
#if WITH_UNIX
   case AF_UNIX:
      fprintf(outfile, "unix%s%s %s",
	      opttype==SOCK_DGRAM?"datagram":"",
#ifdef SO_ACCEPTCONN
	      optacceptconn?"(listening)":
#endif
	      "",
	      sockaddr_unix_info(&sockname.un, namelen,
				 namebuff, sizeof(namebuff)));
      break;
#endif
#if WITH_IP4
   case AF_INET:
      switch (opttype) {
#if WITH_TCP
      case SOCK_STREAM:
	 fprintf(outfile, "tcp%s %s %s",
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet4_info(&sockname.ip4,
				     namebuff, sizeof(namebuff)),
		 sockaddr_inet4_info(&peername.ip4,
				     peerbuff, sizeof(peerbuff)));
	 break;
#endif
#if WITH_UDP
      case SOCK_DGRAM:
	 fprintf(outfile, "udp%s %s %s",
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet4_info(&sockname.ip4,
				     namebuff, sizeof(namebuff)),
		 sockaddr_inet4_info(&peername.ip4,
				     peerbuff, sizeof(peerbuff)));
	 break;
#endif
      default:
	 fprintf(outfile, "ip %s",
		 sockaddr_inet4_info(&sockname.ip4,
				     namebuff, sizeof(namebuff)));
	 break;
      }
      break;
#endif /* WITH_IP4 */

#if WITH_IP6
   case AF_INET6:
      switch (opttype) {
#if WITH_TCP
      case SOCK_STREAM:
	 fprintf(outfile, "tcp6%s %s %s",
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet6_info(&sockname.ip6,
				     namebuff, sizeof(namebuff)),
		 sockaddr_inet6_info(&peername.ip6,
				     peerbuff, sizeof(peerbuff)));
	 break;
#endif
#if WITH_UDP
      case SOCK_DGRAM:
	 fprintf(outfile, "udp6%s %s %s",
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet6_info(&sockname.ip6,
				     namebuff, sizeof(namebuff)),
		 sockaddr_inet6_info(&peername.ip6,
				     peerbuff, sizeof(peerbuff)));
	 break;
#endif
      default:
	 fprintf(outfile, "ip6 %s",
		 sockaddr_inet6_info(&sockname.ip6,
				     namebuff, sizeof(namebuff)));
	 break;
      }
#endif /* WITH_IP6 */
   default:
      fputs("socket", outfile);
   }

   return result;
#undef FDNAME_OPTLEN
#undef FDNAME_NAMELEN
}
#endif /* WITH_SOCKET */