/*
 * execcmd.c
 * 
 * Copyright 2025 osboxes <osboxes@osboxes>
 * 
 * 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.
 * 
 * foreground = el padre espera por la 
 *      finalizacion del proceso hijo
 * background = el padre NO espera por la finalizacion del proceso hijo
 * identificamos procesos en background indicando "&" al final del comando     
 * 
 * Igual que shell3 pero:
 *  -agregamos se#al de alarma cada 4 segundos para la ejecucion funcion mataZombies()
 * 
 * Programa shell foreground y background primitivo, con 
 * las siguientes limitaciones:
 *   1. no admite parametros en los comandos
 *   2. comandos de largo hasta LARGOCMD
 *   3. genera zombies hasta que se ejecuta la funcion mataZombies()
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
#define LARGOCMD 80

// devuelve la cantidad de comandos que hay
int cantidadComandos(char *);
void copiarComando(char *destino,char *origen,int ncmd);
void ltrim(char *destino,char *origen);
void rtrim(char *destino);
void ejecutoForeground(char *cmd);
void ejecutoBackground(char *cmd);
void mataZombies();
void sig_alarma(int);

int main(int argc, char *argv[])
{
	int i,n;
	char cmd[LARGOCMD], lcmd[LARGOCMD], scmd[LARGOCMD];
	// le digo al SO que ejecute a la funcion/handle sig_alarma
	// cuando arribe la se#al SIGALRM a este proceso
	signal(SIGALRM,sig_alarma);
	// programo una alarma para dentro de 4 segundos aprox
	alarm(4);
	do {
		printf("$$");
		fgets(cmd,LARGOCMD,stdin);
		cmd[strlen(cmd)-1]='\0';
		// quito espacios delante y atras
		ltrim(lcmd,cmd);
		rtrim(lcmd);
		//TRACE
		//printf("cmd = [%s]\n",lcmd);
		if ( strlen(lcmd) == 0 ) continue; // si no hay nada que ejecutar.. itero nuevamente
		if ( strcmp(lcmd,"fin") == 0 ) break; // salida al estilo jromer!
		//TRACE
		//printf("En [%s] hay %d comandos\n",lcmd,cantidadComandos(lcmd));
		n=cantidadComandos(lcmd);
		for(i=1;i<=n;i++) {
			copiarComando(scmd,lcmd,i);
			// quito espacios delante y atras
			ltrim(cmd,scmd);
			rtrim(cmd);
			//TRACE
			//printf("cmd = [%s]\n",cmd);
			if ( cmd[strlen(cmd)-1] == '&' ) {
				// background
				// quito &
				cmd[strlen(cmd)-1] = '\0'; 
				// al quitar el & final, puede que ahora haya espacios al final
				rtrim(cmd);
				ejecutoBackground(cmd);
			} else {
				// foreground
				ejecutoForeground(scmd);
			}
		}
	} while( 1 ); // loop jromer!
	 
	return 0;
}

// devuelve la cantidad de comandos que hay
int cantidadComandos(char *cmd) {
	int i=0,n=0;
	if ( strlen(cmd) == 0 ) return 0;
	n=1;
	while(cmd[i]) {
		if ( cmd[i] == '&' ) {
			i++;
			while(cmd[i] && cmd[i] == ' ') i++;
			if ( cmd[i] ) n++;
		}
		i++;
	}
	return n;
}

// copia en to a partir de origen el comando numero ncmd
void copiarComando(char *to,char *cmd,int ncmd) {
	int i=0,ii=0,n=0;
	if ( strlen(cmd) == 0 ) return;
	n=1;
	while(cmd[i]) {
		if (n == ncmd) {
			*(to+ii) = cmd[i];
			ii++;
		}
		if ( cmd[i] == '&' ) {
			i++;
			while(cmd[i] && cmd[i] == ' ') i++;
			if ( cmd[i] ) n++;
			if (n == ncmd) {
				*(to+ii) = cmd[i];
				ii++;
			}			
		}
		i++;
	}
	*(to+ii) = '\0';
}

// devuelve en destino origen menos los espacios iniciales
void ltrim(char *destino,char *origen) {
	if ( origen == NULL || destino == NULL ) return;
	while(*origen && *origen == ' ') origen++;
	while(*origen ) { 
		*destino = *origen;
		origen++;destino++;
	}
	*destino='\0';
}

// quita de destino los espacios que hay al final
void rtrim(char *destino) {
	if ( destino == NULL ) return;
	// p apunta al \0 final de destino
	//TRACE string antes de hacer rtrim
	//printf("rtrim: [%s]\n",destino);
	char *p = destino + strlen(destino);
	while(*(--p) == ' ') *p='\0';
	//TRACE string despues de hacer rtrim
	//printf("rtrim: [%s]\n",destino);
}

// funcion que ejecuta cmd y hace wait() 
void ejecutoForeground(char *cmd) {
	int ret;
	pid_t pid = fork();
	if ( pid > 0 ) {
		// padre, espero SOLO por la finalizacion de este proceso
		pid = waitpid(pid,&ret,0);
		printf("PID %d [%s] foreground retorno %d\n",pid,cmd,ret/256);
	} else {
		// hijo -> exec()
		ret = execlp(cmd,cmd,NULL);
		printf("PID %d error en comando foreground [%s] exec retorno %d este PID retorna 1\n",getpid(),cmd,ret);
		exit(1);
	}
}

// funcion que ejecuta cmd y no hace wait() 
void ejecutoBackground(char *cmd) {
	int ret;
	pid_t pid = fork();
	if ( pid > 0 ) {
		// padre
		printf("PID %d [%s] background creado..\n",pid,cmd);
	} else {
		// hijo -> exec()
		ret=execlp(cmd,cmd,NULL);
		printf("PID %d error en comando background [%s] exec retorna %d este PID retorna 1\n",getpid(),cmd,ret);
		exit(1);
	}
}

// eventualmente el shell ejecuta esta funcion para eliminar los zombies
void mataZombies() {
	pid_t pid;
	int ret;
	//printf("mataZombies()!\n");
	// -1 -> espera por cualquier hijo
	// WNOHANG -> llamada no bloqueante de waitpid()
	while((pid = waitpid(-1,&ret,WNOHANG)) > 0) {
		printf("PID %d finalizo y retorno %d\n",pid,ret/256);
	}
}

//handle de la se#al de alarma
void sig_alarma(int signo) {
	//printf("sig_alarma(): signo = %d\n",signo);
	// verifico si hay zombies cada 4 segundos aprox!
	mataZombies();
	alarm(4);
}
