/*Copyright (C) 2007 Matteo Lucarelli
* 
*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.
*
*You should have received a copy of the GNU General Public License
*along with this program; if not, write to the Free Software
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
******************************************************************************/

// procutils are small functions to parse proc directory under linux
// getpidlist: read the list of running process ids
// getbinname: from the pid obtains the binary name
// getcmdline: read the command line for a running pid
// parsebinname: extract the binary name from command line
// parsecmdline: parse the command line in single string args
// getworkingdir: get the absolute working directory of pid (even if chrooted)
// getstdstream: get the standard streams opened by the process

#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

// get the running pid list
// return the count - 0 on error
//
//	pid_t pidlist[256];
//	int count = getpidlist(pidlist, 256);
//
int getpidlist(pid_t *pidlist, int maxcount)
{
	int count=0;
	DIR* pdir=opendir("/proc/");
	if (!pdir) return 0;
	
	struct dirent *ep;
	
	while ((ep=readdir(pdir))&&(count<maxcount)){
		pidlist[count]=(pid_t)atol(ep->d_name);
		if (pidlist[count]!=0) count++;
	}
	return count;
}

// get the complete cmdline (not a string!)
// return the size - 0 on error
//
//	char cmdline[1024];
//	size_t size=getcmdline(xxxx,cmdline,1024);
//
size_t getcmdline(pid_t pid, char* cmdline, size_t maxdim)
{
	int fd;
	char tmp[maxdim];
	size_t lenght;
	char* start;
	
	memset(cmdline,0,maxdim);
	
	snprintf(tmp,maxdim,"/proc/%i/cmdline",(int)pid);
	
	if ((fd=open(tmp,O_RDONLY))==0) return 0;
	lenght=read(fd,tmp,maxdim);
	close(fd);
	
	memcpy(cmdline,tmp,lenght);
	return lenght;
}

// get the binary name (as string) corresponding to pid
// return the lenght of name on success, 0 on error
//
//	char binname[128];
//	size_t size = getbinname(xxxx,binname,128);
//
size_t getbinname(pid_t pid, char *name, int namedim)
{
	int fd;
	char tmp[namedim];
	size_t lenght;
	char* start;
	
	snprintf(tmp,namedim,"/proc/%i/cmdline",(int)pid);
	
	if ((fd=open(tmp,O_RDONLY))==0) return 0;
	lenght=read(fd,tmp,namedim);
	close(fd);
	
	if (lenght==0) return 0;
	
	start = strrchr(tmp,'/');
	if (start) strncpy(name,start+1,namedim);
	else strncpy(name,tmp,namedim);
	
	return (strlen(name));
}

// parse binary name (as string) from command line
// return the size - 0 on error
//
//	char cmdline[1024];
//	char binname[128];
//	if (getcmdline(xxxx,cmdline,1024))
//		parsebinname(cmdline,binname,128);
//
size_t parsebinname(char* cmdline, char* binname, size_t maxdim)
{
	char* start=strrchr(cmdline,'/');
	
	if (start) strncpy(binname,start+1,maxdim);
	else strncpy(binname,cmdline,maxdim);

	return (strlen(binname));
}

// parse command line in single strings
// return the size - 0 on error
//
//	char cmdline[1024];
//	char* argv[128];
//	size_t cmdlinedim;
//	int argc;
//	if (cmdlinedim=getcmdline(xxxx,cmdline,1024))
//		argc=parsecmdline(cmdline,cmdlinedim,argv,128);
//
int parsecmdline(char *cmdline, size_t cmdlinedim, char **argv, int maxarg)
{
	int argc=0;
	char* start=cmdline;
	while ((cmdline-start)<cmdlinedim && argc<maxarg){
		argv[argc]=cmdline;
		cmdline+=strlen(argv[argc])+1;
		argc++;
	}
	return argc;
}

// get the absolute working directory of pid
//
//	char wdir[1024];
//	size_t size=getworkingdir(xxxx,wdir,1024);
//
size_t getworkingdir(pid_t pid, char* wdir, size_t wdirdim)
{
	int fd;
	char tmp[wdirdim],chroot[wdirdim];
	size_t tot;
	
	snprintf(tmp,wdirdim,"/proc/%i/root",(int)pid);
	readlink(tmp,chroot,wdirdim);
	
	if (!strcmp(chroot,"/")){
		*chroot=0;
		tot=0;
	}else tot=strlen(chroot);
	
	snprintf(tmp,wdirdim,"/proc/%i/cwd",(int)pid);
	readlink(tmp,wdir,wdirdim);
	
	tot+=strlen(wdir);
	strncat(chroot,wdir,wdirdim);
	return (tot);
}

// get the standard streams (0:in ,1:out , 2:err) of pid
// string returned can be path or symbolyc (pipe:..., socket:...)
// pass NULL if some streams don't care
// return the number of streams readed (0 on error)
//
//      char pin[1024];
//      char pout[1024];
//      char perr[1024];
//      getstdstream(xxxx,pin,pout,perr,1024);
//
int getstdstream(pid_t pid, char* pin, char* pout, char* perr, size_t maxchar)
{
	int count=0;
	char tmp[256];
	
	if (pin){
		memset(pin,0,maxchar);
		snprintf(tmp,256,"/proc/%i/fd/0",(int)pid);
		if (readlink(tmp,pin,maxchar-1)!=-1) count++;
	}
	if (pout){
		memset(pout,0,maxchar);
		snprintf(tmp,256,"/proc/%i/fd/1",(int)pid);
		if (readlink(tmp,pout,maxchar-1)!=-1) count++;
	}
	if (perr){
		memset(perr,0,maxchar);
		snprintf(tmp,256,"/proc/%i/fd/2",(int)pid);
		if (readlink(tmp,perr,maxchar-1)!=-1) count++;
	}

	return count;
}