PROYECTO PRÁCTICO PR7
(Gestión de Procesos 7)
El objetivo de este proyecto práctico es la implementación
de un sencillo intérprete de mandatos en UNIX/Linux.
El programa a desarrollar ha de seguir las especificaciones
y requisitos contenidos en este documento.
1. Descripción.
El
intérprete de mandatos a desarrollar o minishell va
a utilizar :
-
la entrada estándar (descriptor de archivo 0) para leer las líneas
de mandatos que interpreta y ejecuta.
-
la salida estándar (descriptor de archivo 1) para presentar el resultado
de los comandos internos.
-
la salida de error estándar (descriptor de archivo 2) para mostrar
las variables especiales prompt y bgpid así como para
notificar los errores que se puedan dar. Si ocurre un error en alguna llamada
al sistema, se utiliza para notificarlo la función de librería
perror.
NOTA: La variable prompt representa el mensaje que muestra el
shell en espera del comando del usuario.
La variable bgpid representa el PID del proceso que se ha enviado
a ejecutar en el background.
Para el desarrollo de esta práctica se proporciona un
parser
que permite leer los mandatos introducidos por el usuario. Para realizar
la práctica sólo tendreis que preocuparos de implementar
el intérprete de mandatos.
La
sintaxis que se propone para parser que se os proporciona es similar
a la sintaxis que comprende el parser del intérprete de mandatos
del sistema operativo UNIX, y es la siguiente:
-
Blanco: Es un carácter tabulador
o espacio.
-
Separador: Es un carácter con
significado especial (|<>&), el fin de línea o el fin
de archivo (por teclado CTRL-D).
-
Texto: Es cualquier secuencia de caracteres
delimitada por blanco o separador.
-
Mandato: Es una secuencia de textos
separados por blancos. El primer texto especifica el nombre del mandato
a ejecutar. Las restantes son los argumentos del mandato invocado. El nombre
del mandato se pasa como argumento 0 (man execvp). Cada mandato
se ejecuta como un proceso hijo directo del minishell (man fork).
El valor de un mandato es su estado de terminación (man 2 wait).
Si la ejecución falla se notifica el error (por el estándar
error).
-
Secuencia: Es una secuencia de dos
o más mandatos separados por '|'. La salida estándar de cada
mandato se conecta por una tubería (man pipe) a la entrada
estándar del siguiente. El minishell normalmente espera la
terminación del último mandato de la secuencia antes de solicitar
la siguiente línea de entrada. El valor de una secuencia es el valor
del último mandato de la misma.
-
Redirección: La entrada o la
salida de un mandato o secuencia puede ser redirigida añadiendo
tras él la siguiente notación. En caso de cualquier error
durante las redirecciones, se notifica (por la salida de error estándar)
y se suspende la ejecución de la línea.
-
< archivo: Usa archivo como entrada estándar abriéndolo
para lectura (man open).
-
> archivo: Usa archivo como salida estándar. Si el archivo no existe
se crea, si existe se trunca (man creat), modo de creación
0666.
-
>& archivo: Usa archivo como estándar error. Si el archivo no
existe se crea, si existe se trunca (man creat), modo de creación
0666.
-
Background (&): Un mandato o secuencia terminado en '&' supone
la ejecución asíncrona del mismo, esto es, el minishell
no queda bloqueado esperando su terminación. Ejecuta el mandato
sin esperar por él imprimiendo por pantalla el identificador del
proceso (bgid) por el que habría esperado con el siguiente
formato "[%d]\n".
Prompt: Mensaje de apremio antes de leer cada línea. Por defecto
será "msh> ".
1.1 Obtención de la línea de mandatos leída
Para el desarrollo de esta práctica se os proporciona código
de apoyo que se describe más abajo (msh.tgz).
Para obtener la línea de mandatos tecleada por el usuario debe
utilizarse la función obtain_order
cuyo prototipo es el siguiente:
int
obtain_order(char ***argvv, char **filev, int *bg);
La llamada devuelve 0 en caso de teclear Control-D (EOF), -1 si se encontró
un error. Si se ejecuta con éxito la llamada devuelve el número
de mandatos más uno. Así, por ejemplo:
-
para ls -l devuelve 2
-
para ls -l | sort devuelve 3
El argumento argvv permite tener
acceso a todos los mandatos introducidos por el usuario. Con el argumento
filev
se pueden obtener los ficheros utilizados en la redirección:
-
filev[0] apuntará al nombre del fichero a utilizar en la redirección
de entrada en caso de que exista, o NULL si no hay ninguno.
-
filev[1] apuntará al nombre del fichero a utilizar en la redirección
de salida en caso de que exista, o NULL si no hay ninguno.
-
filev[1] apuntará al nombre del fichero a utilizar en la redirección
de la salida de error en caso de que exista, o NULL si no hay ninguno.
El argumento bg es 1 si el mandato
o secuencia de mandatos debe ejecutarse en background.
Si el usuario teclea ls -l | sort <
fichero. Los argumentos anteriores tendrán la disposición
que se muestra en la siguiente figura.
Figura 1: Disposición de los argumentos en argvv.
En el archivo main.c
que se os proporciona (archivo que debeis rellenar con el código
del minishell) se invoca a la función obtain_order
y se ejecuta el siguiente bucle:
for
(argvc = 0; (argv = argvv[argvc]); argvc++) {
for (argc = 0; argv[argc]; argc++)
printf("%s ", argv[argc]);
printf("\n");
}
if (filev[0]) printf("< %s\n", filev[0]);/* IN */
if (filev[1]) printf("> %s\n", filev[1]);/* OUT */
if (filev[2]) printf(">& %s\n", filev[2]);/* ERR */
if (bg) printf("&\n");
Estas líneas no sirven para nada, salvo para que os familiariceis
con las estructuras de datos devueltas por obtain_order.
Se recomienda que, sin modificar este fichero, compileis y ejecuteis
este programa minishell, introduciendo diferentes mandatos y secuencias
de mandatos para comprender claramente como acceder a cada uno de los mandatos
de una secuencia.
1.2. Desarrollo del minishell
Para desarrollar el minishell debeis seguir una serie de
pasos, de tal forma que se construya el minishell de forma incremental.
En cada paso se añadirá nueva funcionalidad sobre el anterior.
El minishell consta de un bucle infinito del cual sólo se
sale tras las detección de una condición EOF (un carácter
Control-D) o si se ejecuta el comando interno exit.
En la ejecución del bucle infinito, el minishell (1)
imprime el símbolo del prompt, (2) espera la entrada de comandos
por parte del usuario y (3) los ejecuta. El código de apoyo que
se proporciona en esta práctica contiene el núcleo básico
del minishell para realizar el parsing de las líneas
de entrada. La práctica consiste en modificar el código para,
de forma progresiva, añadir al minishell las siguientes capacidades:
-
Ejecución de mandatos simples del tipo ls
-l , who, etc. Para este
apartado deberá usar las llamadas al sistema fork,
wait
y execve. Para localizar los comandos,
haced uso del contenido de la variable de entorno PATH.
-
Ejecución de mandatos simples con redirecciones (entrada, salida
y de error). Además de las llamadas anteriores deberá usar
las llamadas dup o dup2.
-
Ejecución de mandatos simples en background. No se debe usar wait.
-
Ejecución de secuencias de mandatos conectados por pipes. Usar la
llamada pipe.
-
Tratamiento de señales. Ni el minishell ni los comandos lanzados
en background, deben morir por señales generadas desde teclado (SIGINT,
SIGQUIT) (man signal), por lo tanto éstas son ignoradas.
Por contra, los comandos lanzados en foreground deben morir si le llegan
estas señales, por lo tanto mantienen la acción por defecto.
-
Ejecución de mandatos internos. Un mandato interno es aquél
que bien se corresponde directamente con una llamada al sistema o bien
es un complemento que ofrece el propio minishell. Para que su efecto
sea permanente, ha de ser implementado y ejecutado dentro del propio minishell.
Será ejecutado en un subshell (man
fork) sólo si se invoca en background o aparece en una secuencia
y no es el último.Todo mandato interno comprueba el número
de argumentos con que se le invoca y si encuentra este o cualquier otro
error, lo notifica (por el estándar error) y termina con valor distinto
de cero.
Los mandatos internos del minishell son:
-
cd [Directorio]. Cambia el
directorio por defecto (man 2 chdir).
Si aparece Directorio debe cambiar
al mismo. Si no aparece, cambia al directorio especificado en la variable
de entorno HOME. Presenta (por
la salida estándar) como resultado el camino absoluto al directorio
actual de trabajo (man getcwd) con
el formato: "%s\n".
-
umask [Valor]. Cambia la máscara
de creación de archivos (man 2 umask).
Presenta (por la salida estándar) como resultado el valor de la
actual máscara con el formato: "%o\n". Además, si aparece
Valor (dado en octal, man
strtol), cambia la máscara a dicho valor.
2. Código Fuente de Apoyo
Para facilitar la realización de esta práctica se dispone
del fichero msh.tgz que contiene código
fuente de apoyo. Al extraer su contenido desde el directorio home de tu
cuenta, se crea el directorio msh/, donde se debe desarrollar la
práctica. Dentro de este directorio se habrán incluido los
siguientes ficheros:
-
Makefile
Fichero fuente para la herramienta make. NO debe ser modificado. Con
él se consigue la recompilación automática sólo
de los ficheros fuente que se modifiquen.
-
y.c
Fichero fuente de C. NO debe ser modificado. Define funciones básicas
para usar la herramienta lex sin necesidad
de usar la librería l.
-
scanner.l
Fichero fuente para la herramienta lex.
NO debe ser modificado. Con él se genera automáticamente
código C que implementa un analizador lexicográfico (scanner)
que permite reconocer tokens, considerando los posibles separadores
(\t |<>&\n).
-
parser.y
Fichero fuente para la herramienta yacc. NO debe ser modificado. Con
él se genera automáticamente código C que implementa
un analizador gramatical (parser) que
permite reconocer sentencias correctas de la gramática de entrada
del minishell.
-
main.c
Fichero fuente de C que muestra cómo usar el parser.
Este fichero es el que se DEBE MODIFICAR. Se recomienda estudiar detalladamente
para la correcta comprensión del uso de la función de interfaz,
obtain_order. La versión
que se ofrece hace eco de las líneas tecleadas que sean sintácticamente
correctas (es decir, lo que se ha comentado antes). Esta funcionalidad
debe ser eliminada y sustituida por la ejecución de las líneas
tecleadas.
3. Recomendaciones Generales
-
Desarrolle el minishell por etapas, complicándolo progresivamente.
Comience implementando una versión elemental y continúe introduciendo
la funcionalidad en el orden en que se describen en el apartado Descripción
de la Práctica.
-
Lea detenidamente este documento y sea estricto con la información
en él contenida, en concreto con los formatos de presentación
de los comandos.
-
Lea las páginas de manual a las que se hace referencia.
-
Cuando tenga una idea clara de cómo implementar lo que se le pide,
codifíquelo, compile y pruebe su práctica.