Anda di halaman 1dari 18



Proyecto final minishell

MATERIA: SISTEMAS distribuidos



Cesar lopez castañeda
Gabriela janelly espinoza reyna
Como proyecto final nos fue encomendada la elaboración de un mini
shell distribuido, que pudiera atender a varios clientes.

Para poder realizar este trabajo se tuvo que realizar una investigación
en busca de ejemplos y de todos los elementos que necesitaríamos
para su realización.

Como las bibliografías consultadas son demasiadas, decidimos anexar
los apuntes y los libros electrónicos que consultamos.

Como manera de ejemplificar lo difícil del proyecto anexamos una

copia de la página donde piden los mismo, y que se puede
constatar que uno de los principales problemas a que nos
enfrentamos fue el equipo.

4513 Project 1

A Distributed Shell
Due date: Friday, November 7th, by 11:59pm

• Description
• Examples
• Makefiles
• Experiments
• Hints
• Hand In

The main purpose of this project is to give you some basic experience
in writing a distributed system, while building upon some of your OS
knowledge. You will write a basic remote shell in which a user specifies
an arbitrary shell command to be executed, and it is sent over a
network and executed on a remote server.

You will write a server and a client:

• Server: The server will run on the remote machine. It will bind to
a Unix socket at a port known to the client. When it receives a
connection, it fork()s a child process to handle the connection.
The parent process loops back to wait for more connections. The
child process first verifies that the client is valid via a (clear-text)
password. Then, it executes the given shell command via an
exec() flavored call, returning all stdout and stderr to the client. The
server can assume the shell command does not use stdin. Upon
completing the command, the server will exit. Note, that the
original server process will still be around, waiting for additional
• Client: The client will run on the local machine. From the
command line, the user will specify the host where the server
resides and the command to be executed. The client will then
connect to the server via a socket and transmit the password
and the command. The client will display any output received
from the server to the stdout and then exit.

After you have implemented your Distributed Shell, and debugged it
carefully, you will then design experiments to measure: 1) the amount
of time required (the latency, in milliseconds) to setup a connection to
the server and tear it down, and 2) the maximum throughput (in bits
per second) you can get from the server to the client. For both sets of
measurements, you will need to do multiple runs in order to account
for any variance in the data between runs.

To measure the connection-tear down time, consider forcing the client

to make a call to the the server that does not force the server to do an
exec() or any other significant processing. Since the time scale for the
first test is very small, you will measure the time for many operations
and then divide by the number of operations performed. You will want
to build a harness (a program, shell script, perl script or something
similar) to make repeated connection-tear down requests.

To measure the throughput, consider forcing the server to send a large

file (of a known size) to the client. Note, the client output, by default,
will go to stdout so you may want to consider redirecting stdout to a
file (via the ">" redirection shell operator).

In order to record the time on your computer (instead of, say, looking
at the clock on the wall) you can use the gettimeofday() sytem call from a
program, localtime() from a perl script or time from a shell (note, some
shells have a built-in time command, too).

When your experiments are complete, you must turn in a brief (1-2
page) write-up with the following sections:

1. Design - describe your experiments, including: a) what

programs/scripts you ran and what they did (use pseudo-code);
b) how many runs you performed; c) how you recorded your
data; d) what the system conditions were like; e) and any other
details you think are relevant.
2. Results - depict your results clearly using a series of tables or
graphs. Provide statistical analysis including at least mean and
standard deviation.
3. Analysis - interpret the results. Briefly describe what the results
mean and what you think is happening and any subjective
opinions you may have.

To get help information about specific Unix commands, use the "man"
command. For instance, entering "man tcsh" will display the manual
page entry for the tcsh. If you specify a number after the word "man",
it looks in the indicated section number.

The following system calls for setting up your sockets may be helpful:

• connect()
• accept()
• socket()
• listen()
• bind()
• close()
• send()
• recv()
• getservbyname()
• gethostname()
• gethostbyname()
• gethostbyaddr()
The following system calls might be useful:

• fork() - to create a new process.

• execve() - to execute a file. The call execvp() may be particularly
• getopt() - help in parsing command line arguments.
• strtok() - to help in parsing strings.

The samples code on the course web page contains some samples that
may be useful. In particular:

• talk-tcp.c and listen-tcp.c - helpful samples for doing socket code.

• fork.c - showing the simple use of the fork() call.
• execl.c - showing simple use of the execl() call.
• get-opt.c - code that parses command line arguments (fairly)

You might also see the class slides on sockets.

Beware of the living dead! When the child process of a server exits, it
cannot be reclaimed until the parent gathers its resource statistics
(typically via a wait() call or a variant). You might check out a waitpid()
since wait() blocks, or use wait() in conjunction with a signal() that
triggers when a child exits.

For added security, a "real" server would likely use be chroot() to

change the root directory of the process. For example, many
anonymous ftp servers use chroot() to make the top directory level
/home/ftp or something similar. Thus, a malicious user is unable to
compromise the system.

Note, however, chroot() requires root privilege to run, making it

unavailable to the common user. For developing on your own system
(say, a Linux box in the Fossil lab), I encourage you to explore using
chroot() (do a "man 2 chroot" for more information). Please note if you do
this in a README file or similar documentation when you turn in your

When running your experiments, you need to be careful of processes

in the background (say, a Web browser downloading a page or a
compilation of a kernel) that may influence your results. While multiple
data runs will help spot periods of extra system activity, try to keep
your system "quiet" so the results are consistent (and reproducible).

You might look at the brief slides for some overview information.
Here are some examples. The server:

claypool 94 ccc1% ./server -h

distributed shell server
usage: server [flags], where flags are:
-p # port to serve on (default is 6013)
-d dir directory to serve out of (default is /home/claypool/dsh)
-h this help message

claypool 95 ccc1% ./server

./server activating.
port: 6013
dir: /home/claypool/dsh
Socket created! Accepting connections.

Connection request received.

forked child
received: boomer
password ok
command: ls
executing command...

Connection request received.

forked child
received: boomer
password ok
command: ls -l
executing command...

Connection request received.

forked child
received: boomer
password ok
command: cat Makefile
executing command...

The client (login name boomer) from the same session:

claypool 49 capricorn% ./dsh -h

distributed shell client
usage: dsh [flags] < command >, where flags are:
{-c command} command to execute remotely
{-s host} host server is on
[-p #] port server is on (default is 6013)
[-h] this help message

claypool 41 capricorn% ./dsh -c "ls" -s


claypool 42 capricorn% ./dsh -c "ls -l" -s

total 37
-rw-r----- 1 claypool users 212 Nov 7 22:19 Makefile
-rw-r----- 1 claypool users 997 Nov 1 09:27 client.c
-rwxrwx--- 1 claypool users 6918 Nov 9 00:04 dsh
-rw-r----- 1 claypool users 3790 Nov 9 00:03 dsh.c
-rw-r----- 1 claypool users 5374 Nov 8 23:50 index.html
-rwxrwx--- 1 claypool users 7919 Nov 9 00:09 server
-rw-r----- 1 claypool users 4383 Nov 9 00:09 server.c
-rw-r----- 1 claypool users 240 Nov 7 22:19 server.h
-rw-r----- 1 claypool users 2638 Nov 1 09:36 sock.c
-rw-r----- 1 claypool users 614 Nov 1 09:27 sock.h

claypool 43 capricorn% ./dsh -c "cat Makefile" -s


CC = gcc

all: server dsh

server: server.c server.h

$(CC) -o server server.c

dsh: dsh.c server.h

$(CC) -o dsh dsh.c

sock.o: sock.c sock.h

$(CC) -c sock.c

/bin/rm -f dsh server core *.o *~

You must use a Makefile for this project (it is good for you and good for
us since it makes grading easier). The program make is a useful
program for maintaining large programs that have been broken into
many software modules is make. The program uses a file, usually
named Makefile, that resides in the same directory as the source code.
This file describes how to compile a number of targets specified. To
use, simply type "make" at the command line. WARNING: The
operation line(s) for a target MUST begin with a TAB (do not use
spaces). See the man page for make and gcc for additional information.

# Makefile for distributed shell program

CC = gcc
CFLAGS = -Wall

all: dsh server

server: server.c
$(CC) $(CFLAGS) server.c server.o -o server $(LIBFLAGS)

dsh: dsh.c
$(CC) $(CFLAGS) dsh.c dsh.o -o dsh $(LIBFLAGS)

/bin/rm -rf *.o core dsh server

Hand In
For you client-server shell, the main information I'd like you to have is:

• name(s)
• login(s)
• language
• system dependencies
• brief notes on how to run client and server
• examples of it running

All this information should appear in a README file that accompanies

your program.

Be sure to have your experiment writeup in a clearly labeled file. Use

either text, postscript or pdf format. If you write in Microsoft Word,
save the document as a PDF before turning it in.

You will turn in your assignment online using the turn-in program.
Before you use turnin tar up (with gzip) your files. For example:

mkdir proj1
cp * proj1 /* copy all your files to submit to proj1 directory */
tar -czf proj1.tgz proj1

then copy your files from your Fossil client to your CCC account:
scp proj1.tgz login_name@ccc:~/ /* will ask your ccc passwd */
ssh login_name@ccc /* will ask your ccc passwd */
/cs/bin/turnin submit cs4513 proj1 proj1.tgz



3 * server.c Set up and handle communications with a server process.
4 *
5 * Server Handling copyright 1992-1999, 2001 The Free Software Foundation
6 *
7 * Server Handling is free software.
8 * You may redistribute it and/or modify it under the terms of the
9 * GNU General Public License, as published by the Free Software
10 * Foundation; either version 2, or (at your option) any later version.
11 *
12 * Server Handling is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Server Handling. See the file "COPYING". If not,
19 * write to: The Free Software Foundation, Inc.,
20 * 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 *
23 * As a special exception, The Free Software Foundation gives
24 * permission for additional uses of the text contained in his release
25 * of ServerHandler.
26 *
27 * The exception is that, if you link the ServerHandler library with other
28 * files to produce an executable, this does not by itself cause the
29 * resulting executable to be covered by the GNU General Public License.
30 * Your use of that executable is in no way restricted on account of
31 * linking the ServerHandler library code into it.
32 *
33 * This exception does not however invalidate any other reasons why
34 * the executable file might be covered by the GNU General Public License.
35 *
36 * This exception applies only to the code released by The Free
37 * Software Foundation under the name ServerHandler. If you copy code
38 * from other sources under the General Public License into a copy of
39 * ServerHandler, as the General Public License permits, the exception
40 * does not apply to the code that you add in this way. To avoid
41 * misleading anyone as to the status of such modified files, you must
42 * delete this exception notice from them.
43 *
44 * If you write modifications of your own for ServerHandler, it is your
45 * choice whether to permit this exception to apply to your modifications.
46 * If you do not wish that, delete this exception notice.
47 */
49 #include "fixlib.h"
50 #include "server.h"
52 STATIC volatile enum t_bool read_pipe_timeout;
53 STATIC pid_t server_master_pid = NOPROCESS;
55 tSCC* def_args[] =
56 { (char *) NULL, (char *) NULL };
57 STATIC t_pf_pair server_pair =
58 { (FILE *) NULL, (FILE *) NULL };
59 STATIC pid_t server_id = NULLPROCESS;
60 /*
61 * Arbitrary text that should not be found in the shell output.
62 * It must be a single line and appear verbatim at the start of
63 * the terminating output line.
64 */
65 tSCC z_done[] = "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd";
66 tSCC* p_cur_dir = (char *) NULL;
68 /*
69 * load_data
70 *
71 * Read data from a file pointer (a pipe to a process in this context)
72 * until we either get EOF or we get a marker line back.
73 * The read data are stored in a malloc-ed string that is truncated
74 * to size at the end. Input is assumed to be an ASCII string.
75 */
76 static char *
77 load_data (FILE* fp)
78 {
79 char *pz_text;
80 size_t text_size;
81 char *pz_scan;
82 char z_line[1024];
83 t_bool got_done = BOOL_FALSE;
85 text_size = sizeof (z_line) * 2;
86 pz_scan = pz_text = xmalloc (text_size);
88 for (;;)
89 {
90 size_t used_ct;
92 alarm (10);
93 read_pipe_timeout = BOOL_FALSE;
94 if (fgets (z_line, sizeof (z_line), fp) == (char *) NULL)
95 break;
97 if (strncmp (z_line, z_done, sizeof (z_done) - 1) == 0)
98 {
99 got_done = BOOL_TRUE;
100 break;
101 }
103 strcpy (pz_scan, z_line);
104 pz_scan += strlen (z_line);
105 used_ct = (size_t) (pz_scan - pz_text);
107 if (text_size - used_ct < sizeof (z_line))
108 {
109 size_t off = (size_t) (pz_scan - pz_text);
111 text_size += 4096;
112 pz_text = xrealloc (pz_text, text_size);
113 pz_scan = pz_text + off;
114 }
115 }
117 alarm (0);
118 if (read_pipe_timeout || ! got_done)
119 {
120 free ((void *) pz_text);
121 return (char *) NULL;
122 }
124 while ((pz_scan > pz_text) && ISSPACE (pz_scan[-1]))
125 pz_scan--;
126 *pz_scan = NUL;
127 return xrealloc (pz_text, strlen (pz_text) + 1);
128 }
131 /*
132 * close_server
133 *
134 * Make certain the server process is dead, close the
135 * pipes to it and from it, finally NULL out the file pointers
136 */
137 void
138 close_server (void)
139 {
140 if ( (server_id != NULLPROCESS)
141 && (server_master_pid == getpid ()))
142 {
143 kill ((pid_t) server_id, SIGKILL);
144 server_id = NULLPROCESS;
145 server_master_pid = NOPROCESS;
146 fclose (server_pair.pf_read);
147 fclose (server_pair.pf_write);
148 server_pair.pf_read = server_pair.pf_write = (FILE *) NULL;
149 }
150 }
152 /*
153 * sig_handler really only handles the timeout and pipe signals.
154 * This ensures that we do not wait forever on a request
155 * to our server, and also that if the server dies, we do not
156 * die from a sigpipe problem.
157 */
158 static void
159 sig_handler (int signo ATTRIBUTE_UNUSED)
160 {
161 #ifdef DEBUG
162 /* FIXME: this is illegal to do in a signal handler. */
163 fprintf (stderr,
164 "fixincl ERROR: sig_handler: killed pid %ld due to %s\n",
165 (long) server_id, signo == SIGPIPE ? "SIGPIPE" : "SIGALRM");
166 #endif
167 close_server ();
168 read_pipe_timeout = BOOL_TRUE;
169 }
172 /*
173 * server_setup Establish the signal handler for PIPE and ALARM.
174 * Also establishes the current directory to give to the
175 * server process at the start of every server command.
176 */
177 static void
178 server_setup (void)
179 {
180 static int atexit_done = 0;
181 char buff [MAXPATHLEN + 1];
183 if (atexit_done++ == 0)
184 atexit (close_server);
185 else
186 fputs ("NOTE: server restarted\n", stderr);
188 server_master_pid = getpid ();
190 signal (SIGPIPE, sig_handler);
191 signal (SIGALRM, sig_handler);
193 fputs ("trap : 1\n", server_pair.pf_write);
194 fflush (server_pair.pf_write);
195 getcwd (buff, MAXPATHLEN + 1);
196 p_cur_dir = xstrdup (buff);
197 }
199 /*
200 * find_shell
201 *
202 * Locate a shell suitable for use. For various reasons
203 * (like the use of "trap" in server_setup(), it must be a
204 * Bourne-like shell.
205 *
206 * Most of the time, /bin/sh is preferred, but sometimes
207 * it's quite broken (like on Ultrix). autoconf lets you
208 * override with $CONFIG_SHELL, so we do the same.
209 */
211 static const char *
212 find_shell (void)
213 {
214 char * shell = getenv ("CONFIG_SHELL");
215 if (shell)
216 return shell;
218 return "/bin/sh";
219 }
222 /*
223 * run_shell
224 *
225 * Run a shell command on the server. The command string
226 * passed in is wrapped inside the sequence:
227 *
228 * cd <original directory>
229 * <command string>
230 * echo
231 * echo <end-of-command-marker>
232 *
233 * This ensures that all commands start at a known place in
234 * the directory structure, that any incomplete output lines
235 * are completed and that our special marker sequence appears on
236 * a line by itself. We have chosen a marker that is
237 * excessively unlikely to be reproduced in normal output:
238 *
239 * "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd"
240 */
241 char *
242 run_shell (const char* pz_cmd)
243 {
244 tSCC zNoServer[] = "Server not running, cannot run:\n%s\n\n";
245 t_bool retry = BOOL_TRUE;
247 do_retry:
248 /* IF the shell server process is not running yet,
249 THEN try to start it. */
250 if (server_id == NULLPROCESS)
251 {
252 def_args[0] = find_shell ();
254 server_id = proc2_fopen (&server_pair, def_args);
255 if (server_id > 0)
256 server_setup ();
257 }
259 /* IF it is still not running, THEN return the nil string. */
260 if (server_id <= 0)
261 {
262 fprintf (stderr, zNoServer, pz_cmd);
263 return xcalloc (1, 1);
264 }
266 /* Make sure the process will pay attention to us, send the
267 supplied command, and then have it output a special marker that
268 we can find. */
269 fprintf (server_pair.pf_write, "cd %s\n%s\n\necho\necho %s\n",
270 p_cur_dir, pz_cmd, z_done);
271 fflush (server_pair.pf_write);
273 /* IF the server died and we received a SIGPIPE,
274 THEN return an empty string. */
275 if (server_id == NULLPROCESS)
276 {
277 fprintf (stderr, zNoServer, pz_cmd);
278 return xcalloc (1, 1);
279 }
281 /* Now try to read back all the data. If we fail due to either a
282 sigpipe or sigalrm (timeout), we will return the nil string. */
283 {
284 char *pz = load_data (server_pair.pf_read);
286 if (pz == (char *) NULL)
287 {
288 close_server ();
290 if (retry)
291 {
292 retry = BOOL_FALSE;
293 goto do_retry;
294 }
296 fprintf (stderr, "CLOSING SHELL SERVER - command Failure:\n\t%s\n",
297 pz_cmd);
298 pz = xcalloc (1, 1);
299 }
300 #ifdef DEBUG
301 fprintf( stderr, "run_shell command success: %s\n", pz );
302 #endif
303 return pz;
304 }
305 }



#include <stdio.h> Con las operaciones Linux de bajo nivel de entrada-salida,

#include <stdlib.h> puedes manejar una herramienta llamada “file descriptor”,
#include <unistd.h> /* Para utilizarenexec*/
vez de un “file pointer”. Un “file descriptor” es un valor
de número entero que se refiere a un caso particular de un
#include <signal.h> /*Para señales*/ archivo abierto en un proceso. Este puede ser abierto para
#include <string.h> la lectura, para la escritura, o
#include <sys/wait.h> para ambos (para lectura y la escritura).
#include <sys/types.h>
Incluye los archivos de cabecera <fcntl.h>, <sys/types.h>,
#include <ctype.h> <sys/stat.h>, y <unistd.h> si utilizas cualquiera de
#include <errno.h> las funciones de bajo nivel de entrada y salida
#include <sys/stat.h>
#include <fcntl.h>
extern int errno;

typedef void (*sighandler_t)(int);

static char *my_argv[100], *my_envp[100];
static char *search_path[10];

void handle_signal(int signo)

{ Función que imprime como prompt el nombre de shel l y envía
printf("\n[SHELL NACHO_GABY] "); cualquier dato aún sin escribir al entorno local o a ser escrito en
el fichero; si no, entonces el comportamiento no está definido

void fill_argv(char *tmp_argv)

Esta función lee el archivo, checa el índice, los argumentos, y
{ hace operaciones de cadena entre los argumentos.
char *foo = tmp_argv;
int index = 0;
char ret[100];
bzero(ret, 100);
while(*foo != '\0') {
if(index == 10)

if(*foo == ' ') {

if(my_argv[index] == NULL)
my_argv[index] = (char *)malloc(sizeof(char) *
strlen(ret) + 1);
else {
bzero(my_argv[index], strlen(my_argv[index]));
strncpy(my_argv[index], ret, strlen(ret));
strncat(my_argv[index], "\0", 1);
bzero(ret, 100);
} else {
strncat(ret, foo, 1);
/*printf("foo is %c\n", *foo);*/
my_argv[index] = (char *)malloc(sizeof(char) * strlen(ret) + 1);
strncpy(my_argv[index], ret, strlen(ret));
strncat(my_argv[index], "\0", 1);

void copy_envp(char **envp) Esta función hace un copiado de argumentos, reserva

{ espacio de memoria.
int index = 0;
for(;envp[index] != NULL; index++) {
my_envp[index] = (char *)malloc(sizeof(char) * (strlen(envp[index]) +
memcpy(my_envp[index], envp[index], strlen(envp[index]));
void get_path_string(char **tmp_envp, char *bin_path)
int count = 0;
char *tmp;
while(1) {
Esta función sirve para obtener el path , la ruta del archivo
tmp = strstr(tmp_envp[count], "PATH");
if(tmp == NULL) {
} else {
strncpy(bin_path, tmp, strlen(tmp));

void insert_path_str_to_search(char *path_str)

int index=0;
char *tmp = path_str;
char ret[100]; Esta función sirve para dar un nuevo path, para ello también
se reserva espacio de memoria
while(*tmp != '=')

while(*tmp != '\0') {
if(*tmp == ':') {
strncat(ret, "/", 1);
search_path[index] = (char *) malloc(sizeof(char) * (strlen(ret)
+ 1));
strncat(search_path[index], ret, strlen(ret));
strncat(search_path[index], "\0", 1);
bzero(ret, 100);
} else {
strncat(ret, tmp, 1);

int attach_path(char *cmd)

char ret[100];
int index;
int fd;
bzero(ret, 100);
for(index=0;search_path[index]!=NULL;index++) {
strcpy(ret, search_path[index]); Lee el path, lee pasan el comando como argumento
strncat(ret, cmd, strlen(cmd));
if((fd = open(ret, O_RDONLY)) > 0) {
strncpy(cmd, ret, strlen(ret));
return 0;
return 0;

void call_execve(char *cmd)

{ Esta función checa que el comando este para ser
int i; ejecutado y crear su proceso, en caso de no
encontrarlo manda error y termina.
printf("cmd is %s\n", cmd);
if(fork() == 0) {
i = execve(cmd, my_argv, my_envp);
printf("errno is %d\n", errno);
if(i < 0) {
printf("%s: %s\n", cmd, "Comando no encontrado");
} else {

void free_argv()
Libera espacios reservados en memoria, ya sea de los
{ argumentos.
int index;
for(index=0;my_argv[index]!=NULL;index++) {
bzero(my_argv[index], strlen(my_argv[index])+1);
my_argv[index] = NULL;

int main(int argc, char *argv[], char *envp[]) Programa principal donde se hacen
{ declaración de variables, reservaciones
dinámicas de espacio de memoria.
char c;
int i, fd;
char *tmp = (char *)malloc(sizeof(char) * 100);
char *path_str = (char *)malloc(sizeof(char) * 256);
char *cmd = (char *)malloc(sizeof(char) * 100);
Aquí se mandan ha llamar funciones se la librería
signal(SIGINT, SIG_IGN); signal.h. como la macro SIGINT que es para salir con
signal(SIGINT, handle_signal); una interrupción
INTR (^C) genera SIGINT

get_path_string(my_envp, path_str); Llama funciones, mandando parámetros en este caso el comando

if(fork() == 0) {
execve("/usr/bin/clear", argv, my_envp); Crea un proceso y manda a llamar la función
execve donde le manda un path (ruta absoluta o
relativa del programa) y al terminar se sale.
} else {
printf("[SHELL NACHO_GABY] ");
while(c != EOF) {
Imprime en pantalla el nombre de SHELL
NACHO_GABY, checa el archivo , hace un copiado
entre argumentos ,lee el comando y lo manda a llamar
c = getchar(); con la función execve, en caso de no encontrarlo
switch(c) { manda error.
case '\n': if(tmp[0] == '\0') {
printf("[SHELL NACHO_GABY ] ");
} else {
strncpy(cmd, my_argv[0], strlen(my_argv[0]));
strncat(cmd, "\0", 1);
if(index(cmd, '/') == NULL) {
if(attach_path(cmd) == 0) {
} else {
printf("%s: Comando no encontrado\n",
} else {
if((fd = open(cmd, O_RDONLY)) > 0) {
} else {
printf("%s: Comando no encontrado\n",
printf("[SHELL NACHO_GABY ] ");
bzero(cmd, 100);
bzero(tmp, 100);
default: strncat(tmp, &c, 1);
free(tmp); Libera espacio s de memoria
return 0;

Nos pareció un trabajo muy complejo el cual involucraba una serie de conocimientos que
aunque teníamos bastantes no nos fueron suficientes, tuvimos que investigar de una
manera muy exhaustiva que aunque cansado fue muy motivante y complemento lo
aprendido en clases.
El principal problema con que nos encontramos fue el de el sistema unix, ya que no se
compila igual en cada sistema y existe una gran variedad de códigos de ejemplo que no
pudimos correr debido a esto.


A.S. Tanenbaum y A.S. Woodhull, Sistemas Operativos: Diseño e

implementación, Prentice-Hall, 1998, Segunda Edición.

A. Silberschatz y P. Galvin, Operating System Concepts, Wiley, 2003, Sexta


Advanced Programming in the Unix Environment. by W. Richard Stevens.

Addison-Wesley, Mass. 1992

Advanced Unix Programming. by Marc J. Rochkind. Prentice-Hall, NJ 1985