Sistemas de Operação
Trabalho Prático 2
Ano Lectivo de 2002/2003
Mini-Shell para UNIX
Objectivo
O objectivo principal deste trabalho é fornecer aos alunos um primeiro
contacto com programação avançada em UNIX tendo como base a linguagem
C. Em particular, pretende-se que os alunos sejam capazes de lidar com
argumentos passados através da linha de comando e com chamadas de
sistema para manipulação de processos, tais como: fork,
pipe, dup2, exec, wait,
exit, entre outras.
Requisitos
O trabalho é composto por um conjunto de pequenas etapas que no
conjunto levarão à implementação de uma mini-shell para UNIX. Para
facilitar o desenvolvimento do trabalho cada etapa é apresentada como
que uma extensão da etapa anterior. Sendo assim, o produto final do
trabalho deve ser o ficheiro .c que implemente o maior número de
etapas possível.
A implementação de cada etapa deve ser o mais robusta possível, i.e.,
se uma chamada a uma função do sistema falhar, o programa em execução
deve apresentar uma mensagem de erro apropriada e terminar com um
código de exit correcto. Para tal, procure tirar partido das
funções de sistema cerr, perror ou sterror
para gerar mensagens de erro apropriadas. Como exemplo, considere o
código que se segue (em que syscall deve ser substituído pela
função a usar):
if (syscall(args) == ERRO) {
perror("syscall");
exit(1);
}
Em nenhuma etapa deve fazer uso das funções de sistema system
ou popen. Como alternativa, deve recorrer sempre às funções
de sistema fork, dup2, e da família exec.
Etapas do Trabalho
Segue-se a descrição das várias etapas do trabalho.
Etapa 1: Argumentos da linha de comando
Em UNIX é comum passar argumentos a programas através da linha de
comandos. Uma linha de comando UNIX consiste num conjunto de palavras
(os argumentos) separadas por espaços, tabs, ou backslash seguido do
caracter mudança de linha (\n). Cada palavra é uma string de
caracteres. Quando um utilizador escreve um comando correspondente a
um programa executável, a shell apanha a linha de comando, como uma
string, determina nessa string qual a palavra correspondente ao
comando do utilizador e constroi um vector de strings com os
argumentos a passar ao programa que executa o comando. De seguida cria
um processo filho para executar o programa passando-lhe os argumentos
determinados.
Nesta etapa pretende-se que implemente um programa que mostre uma
prompt ao utilizador (por exemplo, 'msh$ '), leia as strings
correspondentes aos comandos que o utilizador for invocando, e para
cada string lida chame uma função parse para estruturar os
argumentos correspondentes ao comando invocado e uma função
print_parse para imprimir o conteúdo da(s) estrutura(s)
retornada(s) por parse. O programa deve terminar quando o
utilizador invocar o comando exit. Possíveis incorrecções na
invocação dos comandos devem ser detectadas e reportadas através da
função perror.
Sugestões de implementação:
Etapa 2: Comandos sem argumentos
Estenda a mini-shell de forma que consiga executar comandos do
utilizador. Nesta etapa, considere apenas comandos sem
argumentos. Exemplos:
Etapa 3: Comandos com argumentos
Estenda a mini-shell de forma a admitir argumentos para os
comandos. Para simplificar, admita que existe um número máximo de
argumentos para um comando (#define MAXARGS 100). Exemplos:
- date
- more nomeficheiro
- ls -l /tmp
- gcc -s -O -g -o pois pois.c
Etapa 4: Comandos habituais
Estenda a mini-shell de forma a suportar alguns dos comandos internos
mais comuns:
- cd path (utilize a função chdir)
- pwd (utilize a função getcwd)
- echo escreve tudo que se segue
Etapa 5: Modificar variáveis de ambiente
Estenda a mini-shell de forma a que seja possível ver e modificar as
variáveis de ambiente. Para tal a shell deve entender os comandos
setenv e printenv. Exemplos:
- setenv var=valor (sem espaços de ambos os lados do =)
- printenv var
Para mais informação consulte as man-pages para getenv,
putenv e environ.
Etapa 6: Redirecionamento de input
Estenda a mini-shell para lidar com redirecionamento simples de
input. A shell deve procurar pelo caracter < e, caso
encontre, deve tratar a palavra que se lhe segue como o nome de um
ficheiro. O input para o comando (parte antes de <) deve
ser lido do ficheiro em vez do standard input. Se o ficheiro não puder
ser lido, deve ser gerado um erro e o comando não deve ser
executado. Exemplos:
- wc < ficheiro
- more < ficheiro
Etapa 7: Redirecionamento de output
Estenda a mini-shell para lidar com redirecionamento simples de
output. A shell deve procurar pelo caracter > e, caso
encontre, deve tratar a palavra que se lhe segue como o nome de um
ficheiro. O output que o comando (parte antes de >) gera
deve ser enviado para o ficheiro em vez de para o standard output. Se
o ficheiro já existir, deve ser re-escrito. Se o ficheiro não puder
ser criado, deve ser gerado um erro e o comando não deve ser
executado. Exemplos:
- ls > ficheiro
- ps -augx > fich1
Etapa 8: Encadeamento de comandos
Estenda a mini-shell de forma a suportar o encadeamento de comandos
(uso de pipes). A shell deve procurar pelo caracter | e, caso
encontre, deve tratar as palavras que se lhe seguem como sendo um
segundo comando. O output que o comando inicial (parte antes de
|) gera deve ser enviado para uma pipe em vez de para o
standard output. Por sua vez, o segundo comando (parte depois de
|) deve tomar o seu input a partir da pipe utilizada pelo
primeiro comando para enviar o seu output.
Numa segunda fase estenda a mini-shell de forma a que possa encadear
mais do que uma pipe na linha de comandos. Exemplos:
- ls -l | more
- ps -ef | grep bash | wc
Etapa 9: Concorrência
Estenda a sua mini-shell para lidar com concorrência de processos. A
shell deve procurar pelo caracter & e, caso encontre, deve
executar o comando (parte antes do &) concorrentemente com a
própria shell, ou seja, a shell não deverá esperar que o comando em
causa termine para apresentar a prompt e desse modo aceitar novos
comandos. Se o caracter & não for o último caracter da linha
de comandos, deve ser gerado um erro e o comando não deve ser
executado. Exemplos:
- gcc -o pois pois.c &
- du ~ &
Etapa 10: Junção final
Caso tenha resolvido separadamente as funcionalidades das etapas
anteriores, junte agora tudo num só programa de forma a que a
mini-shell aceite a combinação dos vários comandos. Se já tiver todo o
desenvolvimento integrado, não tem nada a fazer nesta etapa. Exemplos:
- ls -l /tmp > fich1 &
- grep Ola < fich1 > fich2
- ps -aux | grep root
- grep Ola < fich1 | wc > fich2 &
Prazos
O trabalho deve ser entregue via web até
8 de Abril de 2003, sendo a sua demonstração feita posteriormente em
horário a marcar.