Programación de Sistemas. TEMA II. Modelo UNIX. Objetivo: Entender la filosofia de programación de sistemas a partir del Modelo UNIX. + UNIX es un sistema operativo multiusuario, multitarea, multithreading. + UNIX consiste en : Kernel --- Se encarga de administrar recursos de cómputo tales como CPU, RAM, Disco,Equipos Periféricos y servicios de Red. Shell --- Se encarga de efectuar la interacción entre el usuario y el kernel del sistema operativo. Ejecuta los programas del usuario. Existen varios tipos de Shell: Bourne Shell, C Shell y Korn Shell. Pero UNIX permite que el usuario defina su propio Shell y lo incorpore dentro del sistema operativo. Dicho shell tiene un conjunto de instrucciones para crear tareas del sistema operativo, que permite combinar varios comandos o programas de usuario y sistema operativo. + Versiones y Estándares Actualmente existen diversas versiones de UNIX, y todas ellas atadas a la plataforma donde se ejecuta. Sin embargo, en la historia de UNIX dos han sido las corrientes, originadas por la forma de administrar, formato de comandos, definición de llamadas al sistema y libreries, construcción del kernel y adminstración de periféricos. Berkeley Software Distribution (BSD) - BSDI, Linux, Ultrix UNIX System V Release 4. Solaris, OSF/1, HPUX, AIX, IRIX. Actualmente la versión soportada por la industria es System V, sin embargo, con el auge de Linux, BSD y System V se han combinado y puesto lo mejor de ambas filosofias. Se han diseñado otros estándares con el objetivo de construir programas portables entre sistemas UNIX y aún no UNIX (por ejemplo NT). Uno de esos estándares es POSIX (Portable Operating System Interface) que es un conjunto de definiciones para que un programador las siga y cumpla la portabilidad. ANSI C es otro estándar que se utiliza para que las aplicaciones sean portables. En este curso se usará ANSI C, POSIX y System V, para permitir que se puedan utilizar en plataforma como OSF/1, Solaris y Linux, entro otras. + Llamadas al sistema y librerias de funciones. Una llamada al sistema son los puntos directos de entrada para que un programa pueda obtener servicios del kernel. Las librerias de funciones son un conjunto de rutinas para implantar las funciones requeridas por ANSI C. Las llamadas al sistema son utilizadas por las librerias de funciones. Las librerias de funciones tienen como objetivo ocultar al usuario algunos aspectos de manejo de recursos de cómputo. Por ejemplo, para poder indicar al kernel que se necesita memoria, se debe utilizar la llamada al sistema brk(), en cambio, la función malloc() es una rutina que asigna memoria y para su funcionamiento utiliza la llamada brk(). Para un programador es indistinto si usa una llamada al sistema o una función, dado que el invoca ambas de la misma forma desde un lenguaje de programación. Se recomienda utilizar llamadas al sistema cuando se necesite utilizar un recurso del sistema operativo de una forma específica. UNIX contiene un conjunto de manuales, divididos en secciones: La seccion 2 está dedicada a llamadas al sistema. La seccion 3 está dedicada a la libreria de funciones C. Ejercicio: + Determinar si las siguientes rutinas son llamadas al sistema o funciones: (Tip, teclear man nombre-rutina, y la página indicara nombre-rutina(numero sección) ) fopen, open, dup, printf, brk, malloc, socket, ioctl. + En el libro el Lenguaje de Programación C de Kernighan, uno de los apéndices describe las rutinas de ANSI C. Explicar cada clasificación y dar dos ejemplos de funciones. + Dar el comando man -s 2 intro para ver todas las llamadas al sistema. + API. La manera de saber como incorporar una llamada al sistema o una función es utilizando las definiciones proporcionadas por la interfaz de programación de aplicaciones o API del sistema. Un API indica la definición de estructuras de datos y rutinas para utilizar cierto servicio del sistema operativo. Normalmente, dichas definiciones son presentadas en un archivo de encabezado de lenguaje C; los cuales se deben incorporar dentro del programa. En los sistemas UNIX, dichos encabezados se encuentran bajo el directorio /usr/include. + Uso del API y compilación en el ambiente UNIX. Para incorporar una llamada al sistema y/o función de ANSI C, se deben seguir los siguientes pasos: 1) Determinar que llamada o funcion se va a utilizar. 2) Checar en el manual del sistema acerca de la llamada o funcion e incluir los archivos de encabezado indicados en el manual. Determinar el formato de la rutina. 3) Codificar la lógica del programa. 4) Compilar el programa con el compilador cc o gcc. 5) Generar el programa ejecutable. Ejemplo: Para leer caracter por caracter de la entrada estandar se debe utilizar una llamada al sistema denominada read. Escribir un programa que lea un caracter y lo imprima a salida estándar utilizando la llamada al sistema write. Pasos 1) Determinar que llamada o funcion se va a utilizar. Se debe utilizar la llamada read() y write(). 2) Checar en el manual del sistema acerca de la llamada o funcion e incluir los archivos de encabezado indicados en el manual. man -s 2 read Despliega esto: #include Su formato es: ssize_t read(int fildes,void *buf, size_t nbyte) para UNIX el tipo de dato size_t y ssize_t es un numero long. Esta función se lee como una función que recibe tres argumentos, el primero un entero que es un descriptor de archivos, el segundo un apuntador al buffer que va a almacenar la información y el tercero, el tamaño del buffer. Retorna el número de bytes leidos. man -s 2 write #include ssize_t write(int fildes,void *buf, size_t nbyte) Esta función se lee como una función que recibe tres argumentos, el primero un entero que es un descriptor de archivos, el segundo un apuntador al buffer que almacenar la inforamación a escribir y el tercero, el tamaño del buffer a escribir. Retorna el número de bytes leidos. Por tanto, se debe incluir el archivo de encabezado #include 3) Codificar la lógica del programa. Este programa debe leer la entrada estándar hasta que encuentre un fin de archivo. Y escribir lo que lee a la salida estándar. /**** Ejemplo 1 Tema II * Programa que lee caracteres de la entrada estándar (teclado) * y lo escribe a la salida estándar (pantalla) ****/ /*Se incluye la definición de las llamadas read y write*/ #include /*Programa principal*/ main() { /*Variable donde se almacena los caracteres, se inicializa a 0*/ char ch=-1; while (ch!='\0') { /* El descriptor de archivo 0 es la entrada estándar, * se indica la direccion del buffer y su tamanio */ read(0,&ch,sizeof(char)); /* El descriptor de archivo 1 es la salida estandar, * se indica la direccion del buffer y su tamanio */ write(1,&ch,sizeof(char)); } } 4) Compilar el programa con el compilador cc o gcc. gcc -c Ejemplo1.c esta compilación debe generar un archivo denominado Ejemplo1.o, que es el programa en código máquina o archivo objeto. La bandera -c indica al compilador que compile el programa. 5) Generar el programa ejecutable. gcc -o Ejemplo1 Ejemplo1.o Esta instrucción indica que se debe generar un ejecutable con el nombre Ejemplo1, la opción -o sirve para indicar dicho nombre, si se suprime esta opcion, genera un archivo denominado a.out. Se debe dar la lista de todos los programas objetos relacionados con el ejecutable. Para ejecutar este programa, teclear Ejemplo1 y dar cualquier texto y salto de carro. El programa concluye al recibir un fin de archivo . + Descriptores de archivos. En UNIX cualquier archivo, directorio o dispositivo periférico es tratado como un archivo. En UNIX, para poder manipular un archivo, cada usuario debe indicar que número de descriptor de archivo va a manipular. En UNIX los tres primeros descriptores de archivos son utilizados para designar lo que se conoce como la entrada estándar, salida estándar y error estándar. Descriptor de Archivo Archivo Comentario 0 Entrada Estándar Teclado, si no se utiliza redireccion 1 Salida Estándar Terminal, si no se utiliza redireccion 2 Error Estándar Terminal, si no se utiliza redireccion, aqui se deben imprimir mensajes de error El redireccionamiento consiste en reemplazar la entrada estándar o salida estándar por otro archivo. Por ejemplo, si no se quiere tomar lectura del teclado, sino de un archivo, se debe utilizar el operador de direccionamiento de entrada <. Para probar este concepto, utilizar el Ejemplo1 Ejemplo1 < Ejemplo1.c Esto lo que hace es desplegar el contendio del archivo Ejemplo1.c (como el comando type de DOS o cat de UNIX). Para direccionar la salida, se utiliza el operador >. Ejemplo1 < Ejemplo1.c > salida es el equivalente a realizar un copy o cp, del archivo Ejemplo1.c a salida. + Archivo de compilación. Makefile Con el fin de automatizar las tareas de compilacion y evitar que el programador este proporcionando de una forma repetitiva dos o mas instrucciones, UNIX ofrece una herramienta denominada make. Esta herramienta lee un archivo denominado Makefile, que contiene las instrucciones de compilacion y generación de un programa ejecutable. Un archivo Makefile se compone de las reglas de compilación de un programa. Por ejemplo, el archivo Makefile del programa anterior: #Makefile para los ejemplos del capítulo 2. Ejemplo1: Ejemplo1.o gcc -o Ejemplo1 Ejemplo1.o Ejemplo1.o: Ejemplo1.c gcc -c Ejemplo1.c clean: rm *.o rm Ejemplo1 La primera regla se puede leer asi: Su nombre es Ejemplo1 y se sigue de dos puntos (sin espacio) y se indican las dependencias con otras reglar para poder ejecutar la instrucción. En este caso, para generar el ejecutable Ejemplo1, se debe tener creado el archivo Ejemplo1.o. Si este archivo existe, manda a ejecutar el compilador para generar el ejecutable. La linea que tiene la sentencia de compilación debe tener un tabulador como primer caracter, de lo contrario, falla la ejecución. Ejemplo1: Ejemplo1.o gcc -o Ejemplo1 Ejemplo1.o Si el archivo Ejemplo1.o no existe, entonces, busca una regla para generarlo, en esta caso es: Ejemplo1.o: Ejemplo1.c gcc -c Ejemplo1.c En este caso, esta regla indica que Ejemplo1.o depende de la existencia del programa Ejemplo1.c (generado por el programador), y para generar el archivo Ejemplo1.o debe ejecutar el comando gcc con opción -c sobre el código fuente. La regla clean sirve para indicar las instrucciones necesarias para borrar los programas ejecutables y objeto. Para usar este archivo Makefile, se debe teclear desde la linea de comandos: make Ejemplo1 gcc -c Ejemplo1.c gcc -o Ejemplo1 Ejemplo1.o este genera el archivo ejecutable. Para limpiar. make clean rm *.o rm Ejemplo1 Ejercicio. + Construir un programa que lea caracteres de la entrada estándar y lleve el conteo de caracteres leidos. Al finalizar la lectura del entrada estándar, imprimir el número de caracteres leidos. Usar la llamada al sistema read para leer los caracteres. Imprimir el resultado con un printf. Modificar el archivo Makefile e incluir las instrucciones para generar el programa objeto y ejecutable. + En que formato deja los programas ejecutables ? (checar el comando file de UNIX) + Investigar en los manuales del sistema, o en Internet o en el libro de UNIX Programación práctica, apendice A, mas sobre el comando make y la sintáxis del archivo Makefile. Es necesario que entiendas que un Makefile contiene instrucciones en un lenguaje especial para compilar, generar ejecutables y mantener código fuente. + Lista de argumentos y variables del entorno. La filosofía de UNIX es que sus comandos reciban parámetros o argumentos para que dichos comandos varien su comportamiento según las necesidades del usuario. Para esto, el sistema operativo permite al programador obtener dichos argumentos y manipularlos dentro del programa. Para esto utiliza dos variables, que son pasadas como argumentos de la rutina main() del programa. El primer argumento, que es de tipo entero, y que normalmente es denominada argc (pero no es forzoso llamarla así), indica el número de argumentos que se reciben en la linea de comandos. El segundo argumento es un arreglo de apuntadores a cadenas de caracteres ( char * argv[]). El siguiente ejemplo ilustra este concepto: /**** Ejemplo 3 Tema II * Programa que lee los argumentos de la linea de comandos * y los imprime a la salida estándar (pantalla) ****/ #include main(int argc, char * argv[]) { int i; for (i=0;i main(int argc, char * argv[], char * envp[]) { int i; for (i=0;envp[i]!= (char *) 0;i++) printf("%s\n",envp[i]); } Al ejecutar este programa Ejemplo4 aparece CLASSPATH=/opt/corba:/opt/weblogic/classes:/opt/weblogic/lib/weblogicaux.jar:. DISPLAY=:0.0 HELPPATH=/usr/openwin/lib/locale:/usr/openwin/lib/help HOME=/opt/gus HZ=100 JACORB=/opt/corba LANG=C LD_LIBRARY_PATH=/opt/weblogic/lib/solaris LOGNAME=gus MAIL=/var/mail/gus MANPATH=/usr/openwin/share/man:/usr/man NOSUNVIEW=0 OPENWINHOME=/usr/openwin PATH=/opt/corba/bin:/bin:/opt/FSFgcc/bin:/usr/ccs/bin:/usr/ccs/lib:/usr/openwin/bin:/etc:. PWD=/opt/gus/cibernetica/src/tema2 SHELL=/bin/csh TERM=sun-cmd TZ=Mexico/General USER=gus WEBLOGIC=/opt/weblogic XFILESEARCHPATH=/usr/openwin/lib/locale/%L/%T/%N%S:/usr/openwin/lib/%T/%N%S XINITRC=/usr/openwin/lib/Xinitrc WINDOW_TERMIOS= TERMCAP=sun-cmd:te=\E[>4h:ti=\E[>4l:tc=sun: esta salida puede varia segun la configuración del shell donde se ejecute. Compare esta salida contra el comando setenv (para CSH) o set (para SH o KSH) Ejercicios. + El Ejemplo5 debe utilizar tanto linea de comandos y variables de entorno. Para esto, el Ejemplo5 debe extender al Ejemplo4, pero al recibir un argumento en la linea de comandos, debe insertar en el arreglo de varibles de ambiente la nueva variable. Por ejemplo Ejemplo5 sin ningun argumento, debe comportarse como el Ejemplo4. Pero Ejemplo5 GUSENV=gus debe insertar una nueva variable de ambiente. Imprimir de nuevo todo el arreglo de variables de ambiente + Usuarios y grupos en UNIX. UNIX al ser un sistema multiusuario, debe definir el concepto de usuario. Con este concepto, UNIX controla a los usuarios y les otorga o deniega recursos según el tipo de usuario. Además, se pueden agrupar usuarios en grupos, con el fin de que a un conjunto de usuarios se les puede permitir o denegar acceso a ciertos recursos. Cuando se entra en sesión con UNIX, automáticamente el kernel identifica cada proceso como correspondiente al usuario en sesión. Gracias a esto, un usuario no puede manipular los procesos o archivos de otros usuarios. El único usuario que es capaza de manipular todos los recursos es el superusuario o administrador, conocido como root. Para UNIX, cada usuario y grupo tienen un identificador, que es un número unico. El superusuario tiene el identificador de usuario 0. Checar los archivos /etc/passwd y /etc/group Para poder manipular los usuarios, existen diversas llamadas al sistema. Dos de ellas son las llamadas getuid() y getgid(). Checar el manual del sistema para la descripción de las mismas. El siguiente ejemplo permite obtener el id del usuario y el grupo de la sesión actual. /* Ejemplo 7 Tema II * Obtener el id de usuario y grupo utilizando las llamadas al sistema * getuid() y getgid() */ #include #include main() { printf("uid=%d gid=%d\n",getuid(),getgid()); } Comparar este programa con el comando id de UNIX. Las llamadas getuid y getgid son llamadas que retornan un número entero, y dicho número está definido en el archivo /etc/passwd y /etc/group para usuarios y grupos resepectivamente. Estos identificadores son los que se conocen como Real User Id y Real Group Id. Ahora probemos la seguridad de UNIX. UNIX provee dos llamadas mas, denominadas setuid() y setgid(). Esto permite adquirir los identificadores de otro usuario. Si UNIX no tuviera control sobre esta llamada, cualquier usuario podría tomar el identificador del otro y violar la seguridad!!! (En antaño esto era muy común por errores de programación de los primeros UNIX, y hoy en dia existe en muchos casos con sistemas distribuidos y potencialmente hay varios huecos de seguridad en varios sistemas en Internet) /* Ejemplo 8. Intento de hacker. * Ponerse el uid del usuario, esto se indica desde la linea de comando * El superusuario tiene un uid de 0 */ #include #include #include main(int argc,char * argv[]) { int uid; /*No se recibe nada*/ if (argc == 1) /*poner el uid del usuario actual*/ uid= getuid(); else { /*convertir a entero la linea de comandos*/ uid= atoi(argv[1]); } if (setuid(uid)==-1){ /*error, imprimir con funcion perror*/ perror("Intenta otra vez"); } printf("Hackeo exitoso!!"); } Intente correr Ejemplo8 Hackeo exitoso!!! Ejemplo8 0 Intenta otra vez: Not owner Se puede notar que el primer intento fue exitoso, ya que el usuario si se puede poner su propio identificador. Pero el segundo no es exitoso, ya que no es posible que un usuario se ponga el identificador del superusuario. Si en el segundo intento si se pudo realizar, entonces se ha descubierto un nuevo hueco de seguridad en este sistema!!! Pero si fuera posible entrar como root su Password: ******* Ejemplo8 0 Hackeo exitoso!!! Ejemplo8 100 Hackeo exitoso!!! Y esto se debe a que root puede hacer cualquier cosa. Por eso es que los hackers, al encontrar un sistema UNIX, lo primero que intentan es violar el password de root. Sin embargo, UNIX en algunos casos necesita dar de una forma temporal acceso. Por ejemplo, el comando passwd que cambia el password de un usuario debe escribir sobre un archivo denominado /etc/passwd (o /etc/shadow) que solo pertenece a root. Esto implica que el superusuario es el único capaz de cambiar passwords. Sin embargo, para evitar esta situación, UNIX maneja otro nivel de usuario y grupo, el usuario y grupo Efectivo. El concepto de usuario y grupo Efectivo permite que un programa adquiera de forma temporal el identificador de otro usuario. Para esto, el otro usuario le debe otorgar permisos a los demas. El siguiente ejemplo muestra, en lenguaje C y con algunos comandos de UNIX como es posible esto. /* Ejemplo 9. Otorgar permisos * Ponerse el uid del usuario, esto se indica desde la linea de comando * El superusuario tiene un uid de 0 * Es necesario utilizar el comando chmod para poner permisos setuid. * Se intenta abrir un archivo de solo lectura para otro usuario */ #include #include #include main(int argc,char * argv[]) { int uid; printf("uid=%d gid=%d\n euid=%d egid=%d\n",getuid(),getgid(),geteuid(),getegid()); /*No se recibe nada*/ if (argc == 1) /*poner el uid del usuario actual*/ uid= geteuid(); else { /*convertir a entero la linea de comandos*/ uid= atoi(argv[1]); } if (seteuid(uid)==-1){ /*error, imprimir con funcion perror*/ perror("Checa que tengas el permiso setuid"); } if ( fopen("./protegido.txt","r" == 0) perror("no puedo leer el archivo"); printf("lectura exitosa"); } Para lograr que este programa ayude a entender este concepto: + Compilarlo. + Cambiar permisos chmod 755 Ejemplo9 + Crear el archivo protegido.txt en el mismo lugar donde reside Ejemplo9 cat > protegido.txt hola  + Cambiar a permisos chmod 400 protegido.txt + Pedir al compañero de a lado que ejecute en tu directorio Ejemplo9 uid=1001 gid=1 euid=1001 egid=1 no puedo leer el archivo: Permission denied +Aplicar este comando SOBRE su programa ejecutable. chmod 4755 Ejemplo9 este comando es el que permite el uso del setuid. + Pedir al compañero de a lado que ejecute en tu directorio Ejemplo9 uid=1001 gid=1 euid=0 egid=1 lectura exitosa Qué es lo que sucede. Al momento de generar el programa, este queda con permisos de ejecución para cualquier usuario, aunque con el segundo comando se asegura este hecho. Al crear el archivo protegido.txt, se pone solo de lectura para el propietario. Si otro usuario trata de tener acceso, UNIX no le otorga permisos. Pero si el usuario otorga el permiso 4755, que consiste en "prestar" su id a cualquier usuario o lo conoce como el permiso de setuid, es factible realizar la lectura del archivo protegido. Observese que el euid cambia cuando se aplica el permiso. En esta caso se utilizó root como el usuario propietario de Ejemplo9 y protegido.txt. Un id efectivo solo se puede aplicar sobre programas ejecutables. Un intruso del sistema lo primero que trata de hacer es buscar acceso a la cuenta de administrador. Algunos instantes le bastan para crear comandos parecidos al aquí expuesto y despues ganar acceso completo al sistema. Ejemplo: + En base a lo expuesto,explicar como esta hecho el comando su y passwd de UNIX. + Investigar en Internet sobre huecos de seguridad relacionados con el permiso setuid y: NFS HTTP FTP Correo electrónico o sendmail. + Permisos de archivos y directorios. En el punto pasado se utilizaron permisos sobre archivos. Los permisos de archivos y directorios son lectura (r) para lectura de un archivo o directorio escritura (w) para escritura de un archivo o directorio ejecución (x) para ejecución de un archio o en el caso de un directorio, poder cambiarse a dicho directorio. Además UNIX entiende, para cada archivo, que existen tres tipos de entidades que pueden manipularlo. usuario - el dueño del archivo grupo - el conjunto de usuarios otros - ni el usuario ni el grupo Y cada entidad puede tener los distintos permisos de archivos. Es decir el dueño puede leer,escribir y ejecutar el archivo, pero el grupo al que pertenece el dueño puede solo leerlo y los otros nada. Esto forma una matriz, donde cada entrada de la misma tiene un 0 o 1 si esta el permiso o no. La cuarta columna indica los permisos en base octal lectura(r) escritura(w) ejecución(x) En octal dueño 1 1 1 7 grupo 1 0 0 4 otros 0 0 0 0 Si se aplica un ls -l sobre este archivo, debe desplegar ls -l archivo -rwxr----- ..... Esta representación se puede dar con un número de tres digitos octales, en este caso es 740. Esto se conoce como la máscara de permisos Entonces, para aplicar estos permisos al archivo, se puede aplicar chmod 740 archivo Es posible que un usuario necesite dar permisos de setuid ante la ejecución de un programa, es decir prestar su usuario o tambien su grupo. Para esto UNIX define un cuarto digito, que se denomina el setid y que se puede usar para prestar un id de usuario o grupo. Estos son los dígitos: 4 - prestar id de usuario 2 - prestar id de grupo. Si por ejemplo, el usuario quiere sobre un programa ejecutable, dar el permiso de set user id, y suponiendo que ya tenia la mascar 740 chmod 4740 archivo ls -l archivo rwsr----- observe la "s" en lugar de "x" para el permiso de ejecución. Esto implicar que el usuario que ejecute este programa, obtiene el id del dueño del archivo. Ejercicio + con cualquier archivo, aplicar los siguientes permisos dueño - lectura y ejecución grupo - lectura y escritura otros - ejecución utilizar el comando chmod dando la máscara de permisos. + En UNIX existe un comando denominado "at" que permite programar la ejecución de un programa. Qué significa que tenga un permiso 4755 ? Otro comando es lpstat, que indica el estado de la cola de impresion. Este tiene permiso 4511. Qué significa esto ? Ambos comandos pertenecen al usuario root. + Qué significa que un directorio tenga permiso 700. + checar la descripcion del comando chmod en las páginas del manual + Otros puntos sobre UNIX. + En UNIX no importa la extensión del archivo. El nombre del archivo es cualquier longitud. + El separador de directorios el es caracter / + El caracter \ es usado por el shell para evitar que ciertos caracteres sean interpretados. + En UNIX no existen unidades de disco. Lo que existen son sistemas de archivos, que son montados. + Un floppy es accesado como un sistema de archivos mas. No hay acceso remoto a una unidad de floppy. + Los programas ejecutables de UNIX no pueden ejecutarse en Win32 y viceversa. + Una máquina con UNIX NO debe apagarse sin que el sistema operativo sea dado de baja. + El formato de los archivos de texto UNIX son diferentes a los de Win32, ya que estos últimos hacen una diferencia entre el fin de linea y nueva linea. + Archivos de texto, codigo fuente, si se transfieren via FTP, deben pasarse como ascii, si son archivos binarios (imágenes, ejecutables UNIX) deben pasarse como archivos binarios. + El comando para efectuar compresion de archivos tar cvf - | compress > archivo.tar.Z + El comando para efectuar descompresion de archivos zcat archivo.tar.Z | tar xvf - (nota: tambien exixten herramientas zip para UNIX, pero no es probable que se encuentren en todos los UNIX) + Para poder editar archivos se puede utilizar vi, pico o cualquier editor que grabe en formato ASCII. RESUMEN. Este tema trata de introducir algunos conceptos sobre como UNIX funciona. Se definió el concepto de API y llamadas al sistema como el punto importante para interactuar con UNIX. Se introdujo el concepto de compilación en lenguaje C, así como el uso de herramientas tales como make y sccs para mantener el código. Se explico el concepto de descriptor de archivo y direccionamiento. También el uso de argumentos de la linea de comando y variables de ambiente, que son utilizados por varios comandos de UNIX. El concepto de usuario y grupo fue explicado, haciendo notar la importancia de estos conceptos en el manejo de la seguridad de los recursos. Sobre este mismo punto, se estudió el tipo de permisos sobre los recursos. EJERCICIOS EXTRAS. + Tratar de optimizar el archivo Makefile, para no ser tan repetitivos en la compilación de cada comando. + Localizar el código fuente de pico y bajarlo de Internet. Tratar de compilarlo utilizando el archivo Makefile. + Realizar una comparación del modelo UNIX contra NT. + Construir el comando myPrintf utilizando la llamada write. Investigar como utilizar listas de argumentos variables. Solo recibir cadenas de caracteres para simplificar el problema. RELACION DE LLAMADAS AL SISTEMA EXPUESTAS EN ESTE TEMA. read(2) -- lee un descriptor de archivos write(2) -- escribe a un descriptor de archivos getuid(2) -- obtiene el id de usuario real getgid(2) -- obtiene el id de grupo real geteuid(2) -- obtiene el id de usuario efectivo getegid(2) -- obtiene el id de grupo efectivo setuid(2) -- pone el id de usuario real setgid(2) -- pone el id de grupo real seteuid(2) -- pone el id de usuario efectivo setegid(2) -- pone el id de grupo efectivo