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:

Etapa 4: Comandos habituais

Estenda a mini-shell de forma a suportar alguns dos comandos internos mais comuns:

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: 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:

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:

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:

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:

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:

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.