[[Menú]]

Menú

Introducción

En este capítulo se requieren de conocimientos y conclusiones que anteriormente se han explicado, pero para evitar al lector tener que leer o releer capítulos anteriores volvere a llevar a cabo los estudios y las conclusiones necesarias pero de forma escueta. De este modo este capitulo podra ser autocontenido y el lector no necesitara de otros capitulos para la comprensión de la documentacion.

El menú es la herramienta principal del router, por la cual nosotros realizaremos todas las tareas que desemos o necesitemos. A nuestros ojos el router será como un shell que actúa directamente sobre Linux, pero es MUY IMPORTANTE recalcar que no es así. El menú no deja de ser un simulador de dicho shell, nos muestra una interfaz de línea que es navegable por una estructura que recuerda a la de un sistema de ficheros y nos permite ejecutar comandos como tal; pero la herramienta únicamente se encarga de gestionar la captura del terminal consiguiendo el efecto de intérprete de comandos, generar en MEMORIA DINÁMICA la estructura de datos utilizada para poder navegar por los submenús (esta no se ve reflejada en el sistema de ficheros en ningún momento) y ejecutar ciertas órdenes previamente establecidas (no podemos acceder a ejecutar comandos del SO o del PATH, sin definirlos previamente). Una vez más resaltamos que el SO (en este caso Linux) no es consciente en ningún momento de nuestras operaciones (la herramienta no está empotrado en el sistema), es una aplicación más distinta al intérprete de comandos del SO (shell, bash, …). Por lo tanto es una ejecutable más, que deberemos compilar (dinámicamente como veremos más adelante), crear cierta estructura en el sistema de ficheros (mini-instalación) y ejecutarla para lanzarla.

Mencionar también que esta aplicación no esta diseñada para sustituir al interprete de mandatos del sistema y ejecutarse al arrancar el sistema (aunque esta preparada para que finalmente le sustituya si se desea, creando un script como se vera mas adelante). Con esto se desaconseja sustituir el interprete de comandos de sistema por el poco testeo realizado y x no estar terminada la parte del codigo encargada de la finalizacion inesperada de la aplicacion y de la gestion de de control de finalizacion y captura de las salidas (standar y error) de los programas ejecutados.

En esta situacion (fallo inesperado) si se reemplaza el interprete de comandos podria provocar el reinicio del equipo, sin embargo si se lanza desde el interprete (sin reemplazarlo) y se podruce una situacion inesperada de la aplicacion, siendo necesaria su finalizacion, volveriamos al interprete pudiendo obtener informacion del error que provoco su finalizacion (para solucionarlos) y si deseamos volver a utilizar la mencionada aplicacion (el menu) solo deberiamos volver a ejecutarla en el interpre, sin necesidad de tener que reiniciar la maquina como en el caso anterior.

Los planes iniciales eran que sustituyera al interprete (pero repito que no esta diseñado para este fin), para realizar esto simplemente se tendria que modificarse para que init lance nuestra aplicacion y se realizase tambien la modificacion necesaria para que si en algun caso la herramienta termina de forma inesperada su ejecucion vuelva a lanzarse automaticamente.


En el diseño y desarrollo del menu se ha primado la modularidad, la portabilidad, la claridad y legibilidad del codigo en todo momento de cara a su posterior desarrollo o modificacion. Como esta herramienta tiene que manejar y controlar el terminal a “bajo nivel” al tratar con el terminal en modo “crudo” y hay un mundo entero inabarcable de dispositivo fisicos que habria que controlar optamos por utilizar una biblioteca que realiza esta gestion por nosotros mostrando una api amigable y abstrayendonos de dichas tareas. La biblioteca utilizada es ncurses. Asi que antes de ningun analisis de requisitos de la apliacion hago un breve resumen sobre el funcionamiento y las caracteristicas de ncurses necesarias; tras haber realizado un estudio sobre los DOC's de ncurses.

Ncurses

La biblioteca ncurses es como un intermediario entre por un lado el terminal o los dispositivo fisicos (pantalla y teclado) y por el otro lado la aplicacion. Dicha biblioteca parece tambien estar focalizada en distintos frentes, por un lado de unificar la traduccion de los dispositivos fisicos como son pantallas y teclados (ya que xejemplo no es lo mismo un teclado español q uno ingles) y desligando la portabilidad de la herramienta a un hardware concreto (nosotros hablamos con ncurses y el se encarga en traducir la señal a la pantalla de turno), por otro la configuracion del modo de funcionamiento del terminal (echo, crudo/cocinado, half-delay,….), y por otro (y a mi parecer el principal foco de desarrollo) la gestion de la pantalla, el como mostrar los datos en la pantalla creando sus estructuras (nosotros escribimos en estructuras de ncurses y el gestiona donde tiene q mostrar los caracteres).

El funcionamiento de la biblioteca de ncurses es sencillo, se debe de ejecutar siempre los 4 siguientes bloques:
-Inicializacion ====⇒ Salvaguarda el estado del terminal en ese momento, crea unas nuevas estructuras, necesarias para el uso de la biblioteca.
-Configuracion =====⇒ Se configura los parametros del terminal (modo crudo, echo,..), los atributos (colores, menus, …) y el funcionamiento de las pantallas (auto-refresco, optimizaciones, …)
-Ejecucion =========⇒ Se realizan los calculos y las llamadas a la biblioteca
-Finalizacion ======⇒ Una vez finalizado (el uso de la biblioteca o la apliacion) deberemos “destruir” las estructuras creadas y devolver el el estado salvaguardado del terminal en la inicializacion . En verdad no se destrullen las estructuras, se duermen. Una vez finalizado las estructuras si una aplicacion hace una llamada “refresh()” se vuelven a restaurar el valor de todas esas estructuras con su configuracion.

El punto fuerte de ncurses (aparte de la abstraccion de comunicacion con el dispositivo fisico) es el uso intuitivo de ventanas para representar el conjunto de la ventana. Atraves de un par de llamadas newwin o subwin podemos crear subventana independientes dividiendo la pantalla (mas adelante se describira detalladamente una mejora que se ha añadido relacionada con esta funcionalidad que nos brinda ncurses).

Relacionado con la abstraccion del “nivel” fisico del terminal hay comentar que ncurses tiene una especie de base de datos definida (constantes en codigo) con todos los posibles valores que pueden tomar los caracteres del teclado pero facilitar el desarrollo, la aplicacion creara un subconjunto de estos valores renombrando las constantes que se utilicen en un modulo (constantes.h) asi que si en algun momento se desea añadir control sobre un nuevo caracter se aconseja redefinir (como mas alante veremos) el valor de la constante de ncurses por un valor local en nuestro subconjunto siguiendo la agrupacion realizada. Aunq decir q si se maneja/utiliza el valor de ncurses no habra fallo alguno, simplemente es una cuestion metodica para saber en todo momento que teclas “capturamos”.

Viendo las modificaciones y correcciones de las ultimas versiones nos dimos cuenta que aparecio recientemente (en octubre del año pasado) una nueva version de la biblioteca en cuestion. En esta se habian introducido numerosas correcciones y se habian añadido una valiosa funcionalidad como era era el uso de threads. Crei que esta nueva funcionalidad y las numerosas correcciones podian ser muy interesantes de cara al desarrollo de la apliacion (aunque no se ibaa desarrollar usando threads en la version inicial en una posterior se podria realizar) asi que se descargo e instalo esta ultima version (no presente en los repositorios de ubuntu).

Practicamente toda la inforamcion de ncurses disponible en la red es la misma que dan los creadores en los DOC's de la herramienta, su comprension puede resultar algo ardua pero no deja de seguir siendo intuitiva a grandes rasgos.

NOTA: Esta libreria no ofrece una api versatil y completa sin coste. Es una libreria pesada, el tamaño que ocupa la libreria compilada
llega a ocupar aproximadamente 6,5 MB incluyendo librerias dinamicas y estaticas. En nuestro caso, eliminaremos toda la funcionalidad no
necesaria y unicamente utilizaremos la libreria dinamica; con esto reduciremos el tamaño a 1.6 MB aproximadamente. Teniendo en cuenta
que el tamaño maximo de la particion "DATA" de la flash es de 12,9 MB las librerias ocupan casi el 10% del tamaño, y es un coste muy
alto, pero lo asumiremos.


Una vez explicado las caracteristicas basicas iniciales de la aplicacion sobre cual es y cual no es su funcionalidad y las librerias ncurses, expondre las caracteristicas generales del programa que han condicionado el diseño. Estas caracteristicas se pueden englobar en el control de flujo, las estructuras y la funcionalidad requeridas para la simulacion de CIT.




Estudio

Una vez abordado el problema de la portabilidad ligada a los dispositivos fisicos (pantalla y teclado) necesarios para la aplicacion he realiazado un estudio sobre la funcionalidad que ofrece CIT, la cual la tendremos que clonar en la aplicacion. La cual queda reflejado en la sigueitne tabla:

La funcionalidad de CIT es la siguiente:
—————————————————————————————————–
– Edicion de linea:

TECLA ACCION A REALIZAR
'←' Retrocede el cursor una posicion
'→' Avanza el cursor una posicion
'ins' Desplaza el cursor al principio de la linea
'repag' Desplaza el cursor al final de la linea
'←-' Borra el caracter anterior
'(*)' Se escribe el correspondiente caracter en la posicion de la linea y se avanza el cursor

– Historial de ejecucion

TECLA ACCION A REALIZAR
'cursor arriba' Obtenemos la anterior linea ejecutada
'cursor abajo' Obtenemos la siguiente linea ejecutada

– Navegacion del menu

TECLA ACCION A REALIZAR
'ctrl' + 'p' Desplazamos el puntero del menu al nodo inicial
'enter' Accedemos al nodo correspondiente
Ejecutamos el correspondiente comando
'?' Lista todos los subnodos que tienen la misma raiz que la linea
En el caso de que no haya ninguna letra en la linea (raiz vacia) se listaran todos
'TAB' Autocompleta la palabra de la linea con los nodos posibles



TABLAS DE FUNCIONALIDAD CIT

(*) Simboliza todos los caracteres que aceptaremos como validos, x ejemplo la 'ñ' puede o no estar

Tras jugar con la aplicacion para conseguir la informacion anterior (ya que NO disponimamos de un manual en el que viniese reflejada dixa funcionalidad) examos en falta ciertas funciones de edicion, como son el borrado hacia delante y el retroceso al nodo padre en el menu.

Para poder realizar el diseño de la aplicacion necesitamos ademas de la inforamcion de CIT aspectos importantes que son el flujo de datos o funcionamiento, las estructuras y el configurabilidad. Ahora pasamos a explicar mas en detalle que ha aportado cada aspecto al diseño.

Flujo de ejecucion

En este punto inicial hay que tomar una decision: hacer que el “motor” de la aplicacion sea ad-hoc a esta funcionalidad (estatico) o hacer un “motor” mas dinamico y configurable, tal que de forma “sencilla” se pueda añadir o quitar funcionalidades. Yo me decante por la segunda, un “motor” que sea capaz de ejecutar unas funciones determinadas ante la pulsacion de una tecla o una combinacion de ellas. Asi para añadir o quitar una funcion simplemente habra que crear la funcion deseada y el caracter relacionado (como se dijo anteriormente se aconseja redefinirlo en el modulo correspondiene”.

Pero no vamos a crear un motor inteligente, simplemente el motor sera consciente de unas bases de datos (bases de datos como conjunto de valores, no hay implementeacion como tal) que guarden los caracteres validos y las funciones a ejecutar, y relacionara ciertos caracteres especiales con dichas funciones.
EJ:


teclas validas: numeros (0, 1 ..9,)
teclas especiales: (+, -)
funciones: sumar y restar los elementos de la lista en orden secuencial

relacion
: '+' → funcion sumar
: '-' → funcion restar

Cuando el motor detecte una tecla especial ejecutara la funcion correspondiente.



Por lo tanto nuestro motor tendra como caracteres especiales los indicados anteriormente en la tabla de funcionalidad de CIT, como funciones tendremos aquellas que realicen su labor correspondiente y como caracteres validos las letras, los numeros, el espacio, …

La idea es buena, factible y satisface con creces la escalabilidad y la configurabilidad deseada pero sin embargo choca con la idea de modular el programa, ya que la parte de navegabilidad del menu deberia ser ajena a la edicion de la linea, y segun esta idea las funciones relacionadas con los caracteres especiales tratarian tando la linea (edicion) como el menu (navegabilidad) sin distincion alguna.

Este problema radica en que todos los caracteres especiales los tratamos igual. Por ejemplo a los ojos del “motor” el caracter de borrado ('←-') que solo modifica la linea es igual que el caracter de inicio de menu ('ctrl' + 'p') que unicamente modifica el menu y consecuentementelos trata igual; cuando conceptualmente son dos funciones distintas, segun la modularidad que estamos buscando.

La solucion por la que opte es un poco natural, subdividir los caracteres especiales en 2:
– caracteres internos: aquellos cuya finaldiad sea editar la linea de comandos
– caracteres externos: aquellos cuya finalidad NO tenga que ver con edicion (en este caso navegabilidad)

Las funciones tambien deberian ser catalogadas en dos distintas: funciones internas y externas.Al tomar esta decision la funcionalidad CIT quedaria clasificada del siguiente modo

CARACTER VALIDO FUNCIONALIDAD
'(*)' Se escribe el correspondiente caracter en la posicion de la linea y se avanza el cursor



CARACTER ESP INTERNO FUNCIONALIDAD (FUNCION INTERNA)
'←' Retrocede el cursor una posicion
'→' Avanza el cursor una posicion
'XXX' Desplaza el cursor al principio de la linea
'yyy' Desplaza el cursor al final de la linea
'←-' Borra el caracter anterior
'cursor arriba' Obtenemos la anterior linea ejecutada
'cursor abajo' Obtenemos la siguiente linea ejecutada



CARACTER ESP EXTERNO FUNCIONALIDAD (FUNCION EXTERNA)
'ctrl' + 'p' Desplazamos el puntero del menu al nodo inicial
'enter' Accedemos al nodo correspondiente
Ejecutamos el correspondiente comando
'?' Lista todos los subnodos que tienen la misma raiz que la linea
En el caso de que no haya ninguna letra en la linea (raiz vacia) se listaran todos
'TAB' Autocompleta la palabra de la linea con los nodos posibles



Ahora la funcionalidad de CIT esta ligada al caracter que le representa segun su naturaleza por lo que ahora es factible modular la herramienta. Podemos añadir funcionalidad sobre el menu pero para ello no tendremos que modificar ni tocar nada de la edicion de la linea.

Desde el punto de vista del diseño van surgiendo de forma natural los modulos por los que la aplicacion estara compuesta. Habra 2 modulos principales encargado cada uno de gestionar lo refernte al menu (navegabilidad) y el otro de gestionar lo referente a la edicion de linea (interfaz), En adelante nos referiremos a estos modulos como “MENU” e “INTERFAZ” respectivamente.

Tal cual se ha planteado la solucion hasta ahora el modulo principal sería INTERFAZ, ya que este solo “hablara” con MENU cuando detecte un caracter especial externo. Pero esto no es lo logico ya como se puede intuir estas estructuras tienen el mismo peso, deben sincronizarse, deben ser llamadas para inicializarse y destruirse, … asi que añadiremos un tercer modulo encargado de la sincronizacion de dichos modulos, como la creacion destruccion, ….. Hare referencia a él como “PRINCIPAL”

Resulta intuitivo pensar que las interfaces o llamadas de estos modulos sean las funciones a realizar por cada una (las 7 internas y las 4 externas), es decir INTERFAZ hara las funciones relacionadas con la edicion y MENU las funciones relacionadas con la navegabilidad.

A modo de recopilacion ahora tenemos una nueva jerarquia en el diseño en donde el programa principal se encarga de capturar el caracter por la entrada y si este es de un tipo llama a un modulo y si es del otro tipo llama al otro. Esto choca un poco ya que el la funcionalidad de capturar la entrada deberia ser de INTERFAZ. Ante esto modifique un poco la finalidad de cada modulo, ahora el PRINCIPAL iniciara la apliacion y en vez de leer del terminal llamará a INTERFAZ esperando por un caracter especial externo cuando este lo reciba, le devolvera a principal el caracter externo y este llamara a MENU. Mientras no se reciba un caracter externo interfaz modificara o editara la linea de comandos, ya que sera un caracterer valido o un caracter esp interno, y ambos editan la linea.

Este simple cambio solo afecta a la interfaz del modulo INTERFAZ, ya que antes tenia una por cada funcion interna y ahora solo tendra la de Leer_Entrada que devolvera un valor cuando se reciba un caracter esp externo e internamente hara una seleccion de funcion a realizar segun el caracter.

A groso modo esta es la jerarquia que decidi implantar para la version final. Si es cierto que a modo de ampliacion o desarrollo pensamos otra version en la que PRINCIPAL solo se encarga de inicializar y llamar a un 4 modulo encargado unicamente de la recepcion del caracter y este hiciera la llamada correspondiente al modulo correspondiente. Esta idea esta pensada para separar todo lo referente a la recepcion del caracter por si en un futuro en vez de entrada estandar se quiera leer caracteres de un fichero modificar este modulo y no tener q alterar el resto.

En este punto tenemos una jerarquia inicial con el flujo de control del “motor” ya determinado. Ahora nos queda por resolver las estructuras que guarden la informacion y resolver como mejorar la configuracion del programa (insertar o eliminar caracteres y relacionarlos con las correspondientes funciones, …)

Estructuras

Lo primero que abordaremos sera que por el flujo de datos que he escogido los modulos tienen que “recordar” su estado ya x ejemplo cuando INTERFAZ lea un caracter especial externo dejara su ejecucion, cediendola a PRINCIPAL y luego a MENU pero luego volvera a ejecutar INTERFAZ y este tendra que editar la linea anterior. De igual modo pasa con MENU, por lo que necesitaremos crear unas estructuras para poder recordar el estado de ambos y que sea PRINCIPAL el que gestione esos estados. Con este concepto facilitamos la funciones acerca del historial de linea, ya que se podran simplemente sera gestionar estas estructuras de estado.

Al utilizar ncurses TODA la aplicacion (o todos los modulos) escribe sobre ventanas (utilizando llamadas ncurses) y no por descriptores estandar, asi las entradas Vent_Std y Vent_Err corresponden a la salida y el error estandar.

Antes de entrar a ver por que estaran formadas los estados se puede ver que tendran que inicializarse de algun modo y con algun valor. El responsable de realizar esto será PRINCIPAL, pero como el estado de cada modulo es distinto deben ser los modulos los responsables de iniciarlos. Así en las Interfaces de ambos modulos habra que añadir dos funcion mas, una para crear el estado y otra para destruirlo (MENU tiene 9 funciones e INTERFAZ 3)Estas estructuras tienen la siguiente pinta:

ESTADO DE INTERFAZ ESTADO DE MENU
Vent_Std Vent_Std
Vent_Err Vent_Err
Historial Menu
Prompt
Linea
Cursor

En el estado de MENU existe una entrada “Menu” que hace referencia a la estructura q mantiene para poder navegar (mas adelante se explica). No sera mas que un puntero.

En el estado de INTERFAZ existen cuatro entradas: Historial, Prompt, Linea, Cursor. Empezare por el final:
-Cursor no es mas que un puntero a la posicion donte esta el cursor del interprete.
-Linea y Prompt son del mismo tipo, y conceptualmente son lo mismo, me explicare. He decidido (a lo largo del desarrollo) ver una linea como un conjunto de dos partes, una estatica y otra dinamica: el prompt (estatica) y la linea (dinamica). El motivo para dividir en dos la linea fue que en mas de una ocascion es interesante modificar unicamente el prompt y en otras ocasciones solo la linea. Si las diferenciamos se hara mas rapido.
-Historial, este tipo será simplemente un conjunto de las estructuras de la linea.

Una vez definido los estados y sus estructuras seguiremos con las estructuras de datos. Hay dos grandes modulos (INTERFAZ y MENU) asi q resolveremos las necesidades de cada uno por separado.




Estructuras de datos de INTERFAZ

Lo unico que INTERFAZ debe de mantener es un tipo de estructura que le permita guardar y gestionar lo que entendemos por una linea del interprete. Hay infinidad de tipos para representarla, desde guardarlas en modo crudo directamente en memoria (tiras de caracteres) hasta tipificarla (creando tipos de estructuras ej: lista y pal). Tambien hay que decidir si estas estructuras son estaticas o dinamicas, lo que conlleva a decir que puede haber un maximo o no y en caso de haberlo que valor se le da.

Cada eleccion tiene sus ventajas y sus desventajas, por ejemplo hacerlo crudo es mas eficiente pero conlleva un mayor coste de implementacion o modificacion futura, x el contrario tipificando es mas ineficiente pero mas facil implementarlo o modificar en un futuro.

En estas dos elecciones ha sido la unica vez que la eficiente o velocidad no ha sido el valor principal ha tener en cuenta. La eleccion que se tomo fue crear una estructura que fuese dinamica y tipificada, siendo estas genericas. Al ser dinamica perdemos en eficiencia, pero ganamos en memoria y a estas alturas ya es bien sabido que se dispone de poca; y al utilizar tipos genericos tmb se pierde un poco de eficiente pero a favor se gana un codigo mas legible, mas sencillo y reutilizable (al tratarse de estructuras “genericas”).

De esta forma decidi que para representar una linea lo haria con una estructura generica tipo cadena (almacena punteros a informacion) compuesta por una cabeza y un conjunto de nodos. Y para representar una palabra una estructura propia. Las cuales son así:

Palabra
Inicio
Final
Tam_res

Esta estructura no es dinamica pura, ya que si añadimos un caracter no tenemos que reservar un caracter mas, esta estructura reserva de bloque en bloque de tamaño 'n'. Este tamaño 'n' se puede definir en un modulo (constantes.h). Decir tambien que esta estructura si bien podria haber sido generada en un modulo aparte (x ej: PALABRA) no se ha realizado por consideracion de la velocidad. Se recomienda que se cre un nuevo modulo PALABRA que dependa de INTERFAZ para poder manejar pal's.

Las entradas de su estructura son lo siguiente:
-Inicio apunta al inicio de la palabra.
-Final apunta al final de la palabra que tenia que ser el caracter nulo '\0'.
-Tam_Res es el numero de tamaño reservado para esa palabra.

Cadena (cabeza) Cadena (nodo)
Vent_Std Siguiente
Vent_Err Anterior
Primero Informacion
Puntero
Longitud
Posicion


Esta estructura es mas que conocida por todos, una cadena doblemente enlazada, en la que la cabeza mantiene un puntero al primer nodo y otro puntero que indica el nodo q se esta leyendo. Ademas la cabeza guarda la informacion de posicion y Longitud para hacer la complejidad de sus funciones menores (ej: Longitud O(1),…). Los nodos al tener dos punteros uno al siguiente nodo y otro al anterior puede moverse el puntero con complejidad O(1) tanto hacia delante como hacia detras.

En resumen es una estructura pensada para que sus oepraciones sean lo mas rapidas posibles (para contrarestar la perdida de eiciencia) y tengan una interfaz muy rica y variada.
Esta estructura si que esta implementada en un modulo aparte debido al volumen de su implementacion, ademas de que si en algun momento decidimos utilizar otra forma de representar una linea simplemente deberemos sustituir esa estructura por otra manteniendo la interfaz (consiguiendo asi un punto mas en escalabilidad y modularidad).

Volviendo a las estructuras de INTERFAZ ahora tenemos que tanto “Prompt” como “Linea” pueden utilizar ambos la misma estructura de Cadena de Palabras. Solo nos queda la cuestion del Historial, que no es mas que una cadena circular asi q de q mejor forma implementarla que una cadena de “Lineas”. Ademas el prompt habria q mantenerlo por lo que esta estructura nos resulta como caida del cielo. En resumidas cuentas se ha hexo un codigo muy funcional y tipificado pero muy reutilizable y escalable, simplemente añadiendo el modulo CADENA (usare este nombre en adelante) y creando el tipo Palabra en el modulo INTERFAZ.

Estructuras de datos de MENU

Por su parte el MENU tiene que crear una estructura de datos que sea capaz de ofrecer la navegabilidad vista en el menu CIT sobre la que actuaran las funciones externas (antes vistas). Esta navegabilidad recuerda al sistema de ficheros de linux (conocido de sobra por el lector), pero con ciertos matices que les diferencian.

Tras un estudio sobre el menu CIT hemos obtenido informacion sobre dicho menu e ideas de como simularlo. Este menu es como un arbol de nodos, en el cual hay distintos tipos de nodos (tallo y hoja), salvo xq los tipos de nodos de CIT son un poco especiales, los listo y explico:

 1º Nodos Accesible y se puede atravesar (en adelante puro).\\
 2º Nodos NO accesible pero se puede atravesar (en adelante hibrido). \\
 3º Nodos ejecutable (al igual que en la idea del arbol todas las hojas son ejecutables). \\


Los nodos ejecutable encajan muy bien en el tipo de arbol (siendo hojas), pero los tipos puros e hibridos resulta mas dificl encajarlos en una estructura arbol ya que habria que diferenciar dos tipos de nodos tallos.

Teniendo en cuenta como funciona CIT y las peculiariedades que tiene ahora hay q decidir como implementamos la estructura que soporte el menu. Hay distintas opciones para implementar un arbol de estas caracteristicas a continuacion hago una descripcion y las analizamos una a una:

 1º Utilizar el sistema de ficheros como soporte, siendo los ficheros los nodos ejecutables y los directorios los nodos puros e 
    hibridos, pero para esto tendriamos que crear ficheros con metainformacion sobre el tipo de nodo tallo que es para que el MENU pueda
    gestionar las funciones externas. \\
 2º Crear en codigo maquina (estatico, en tiempo de compilacion) la topologia del arbol utilizando estructuras tipificadas (de forma 
    parecida a los tipos de Linea de INTERFAZ). \\
 3º Leer de un fichero la topologia del arbol y almacenarlo en estructuras tipificadas (como en la opcion 2).\\


Las ventajas y desventajas de estas tres opciones son:

 1º Lo que mas destaca de esta opcion es el alto numero de llamadas al sistema operativo que habria que realizar, ya que tendriamos que 
    leer ficheros por cada directorio, ademas de ver, acceder y retroceder en los directorios constantemente. Esto supone tiempo, aunq 
    bien es cierto que las operaciones son muy sencillas y rapidas, pero es tiempo de mas. Lo que indica peor efectividad. Ademas habría 
    mucho codigo de control sobre la apertura de fichero, lectura de fichero, ... \\
    A su favor tiene que seria un soporte muy modular y portable ya que bastaria con que cuando se telecargue el sistema de ficheros se 
    cargue el de una version u otra, cargando distintas topologias (interesante para tener distintas topologias en distintos modelos de 
    router o para modificar la topologia bastaria con modificar el sistema de ficheros correctamente y reiniciar la herramienta. \\
 2º Seria la opcion mas rapida ya que de antemano se tendrian todos los datos en memoria y simplemente habria que realizar accesos a 
    disco para moverse por el menu, haciendo las comprobaciones necesarias segun el tipo del nodo. Por contra el lado negativo es que es
    un codigo nulo desde el punto de vista de la portabilidad, y complejo ya que para cada modificacion o xa cada topologia distinta 
    habria que recompilar cruzado la topologia, y esto es muy costoso. \\ 
 3º La tercera opcion ha sido por la que me he decantado. Se trata de que a partir de un unico fichero de texto creemos la estructura en 
    memoria siendo luego tan rapida como en la opcion 2. El inconveniente que tiene es que a habria un sobrecoste de carga, lo q implica 
    perdida de velocidad, al iniciar la aplicacion ya que deberia leer el fichero entero e ir creando todos los nodos. Pero el punto que 
    decanta la balanza a su favor es la portabilidad y modularidad. ya que si queremos que distintas versiones tengan su propia 
    topologia bastaria con asociar el fichero  de la topologia a cada uno. Y si queremos modificar o añadir algo a la topologia basta 
    con modificar un fichero (de forma correcta) y reiniciar la herramienta, sin tener que modificar el sistema de ficheros.\\


Al final de este capitulo se explicara la localizacion de este fichero.

Tomada esta decision falta todo lo referente al diseño de este fichero puesto que en base a él MENU debera crear una topologia.

Fichero

Para decir cual es el diseño o patron de este fichero previamente debemos saber que informacion vamos a necesitar para crear el arbol. Esta informacion sera guardada en la estructura de datos de MENU y sera enlazada a la entrada “Menu” del estado de MENU (valga la redundancia); así a partir de dicha estructura obtendremos q valores necesitamos del fichero menu. La estrucutura esta foramada (al igual q cadena) por una cabeza y un conjunto de nodos:

Arbol (Cabeza) Arbol (Nodo) Entrada
Vent_Std Anterior Clave
Vent_Err Max_Hijos Sh
Cima Num_Creados Ayuda
Puntero Hijos Tipo
Info


En las estructuras de Arbol (Cabeza) Cima es un puntero al nodo raiz y Puntero es el puntero al nodo de lectura en cada momento.
En las estructuras de Arbol (Nodo) tenemos 5 entradas:

  1. Anterior: Puntero al nodo padre.
  2. Max_Hijos: Maximo numero de hijos que va a tener el nodo (al ser multicamino).
  3. Num_creados: Numero de nodos hijos ya creados.
  4. Hijos: Puntero a un vector de tamaño Max_Hijos en el que cada elemento es un puntero a 1 hijo .
  5. Info: Puntero a la inforamcion del nodo.


Con estas dos estructuras conseguimos crear un arbol multicamino generico y para que conseguir crear un arbol q soporte el menu CIT necesitamos la informacion de la estructura Entrada, la cual es:

  1. Clave: El nombre del nodo.
  2. Tipo: El tipo de nodo que es (menu puro, menu accesible y ejecutable).
  3. Sh: Script y segun el tipo de nodo tendra un proposito u otro.
  4. Ayuda: Puntero a una tira de caracteres con la ayuda para ese nodo.


Esta estructura contiene la informacion para todos los distintos tipos de nodos y todas las entradas significan lo mismo independientemente del tipo, salvo el caso de sh que segun el tipo que es tiene un proposito u otro, o incluso no tiene.

  • Si es tipo menu puro: no tiene sentido alguno.
  • Si es tipo menu hibirido: no tiene sentido (posteriormente se le dara).
  • Si es tipo ejecutable : significa el nombre del fichero a ejecutar.

Segun esta informacion en el fichero por cada linea habra un nodo que puede ser nodo puro, hibrido o ejecutable y la informacion que debe de almacenar es la del nombre del nodo, el tipo del nodo, el script, la ayuda relacionada con el nodo y ademas en el caso de ser un menu indicar cuantos hijos tiene.

Como podemos ver es mucha informacion para una sola linea, lo que dificulta su comprension o lectura. Tras esta reflexion decidi reducir a lo posible la informacion por linea y tome 2 medidas:

 1º extraer la ayuda a otro fichero, reduciendo considerablemente el tamaño de este y facilitando su lectura. De esta forma ahora se lee 
    primero el fichero que contiene la topologia y las caracteristicas completas de TODOS los nodos y posteriormente se leeria el 
    fichero de ayuda que contendria de nuevo la topologia del arbol con la ayuda correspondiente, pero en este caso no haria falta 
    desarrollar todo el arbol, por si se quiere omitir ayudas a ciertos nodos. 
 2º Diferenciar o distinguir las entradas del fichero si son de menu o si son ejecutables, asi conseguimos por un lado averiguar que 
    tiene hijos, cuantos tiene y ahorrarnos la entrada sh para los menus puros.


En este punto me dispuse a diseñar el formato de los ficheros del manu y de ayuda los cuales son:
PLANTILLA FICHERO MENU

INICIAR ARBOL
#{("Nombre del nodo-padre")-Nº de entradas del nodo\n---->para crear un nodo menu puro
#{("<nombre del nodo-padre>")["nombre del script q comprueba el parametro"]-Nº de entradas del nodo\n ---->para crear un nodo menu hibrido (variable)
HIJOS
#Nº.-"nombre de la entrada":(shQ || shI)["nombre del script"]\n ----> crea un hijo script interno o externo
#Nº.-"Nombre de la entrada":(mA || mU)\n ----> indica que el hijo es un nodo menu puro o un hibrido no variable (ej: meter "add")
#Nº.-"<Nombre de la entrada>":(mU) ----> indica q el hijo es nodo menu hibirido variable (ej: meter interfaz eth0)
FINALIZAR ARBOL
#}("nombre del nodo-padre")/n---->para finalizar un nodo puro o hibrido


PLANTILLA FICHERO AYUDA

INICIAR AYUDA ARBOL
#{("nombre del nodo-padre"):["ayuda del arbol"]----> ayuda del nodo-padre
HIJOS
#("nombre del nodo"):["ayuda del nodo"]----> ayuda del nodo-hoja
FINALIZAR AYUDA ARBOL
#}("nombre del nodo-padre")----> indica fin del nodo padre


Ahora ya sabemos como dar el soporte de navegabilidad a menu solo hace falta indicar q habria q tambien crear un 5º modulo, el cual dependeria de MENU y llamaremos “MULTITREE”. Este contendra todo lo necesario para crear, modificar destruir y almacenar una arbol multicamino.

Como añadido en este punto separamos a un mas la funcionalidad de MENU. Ya que internamente esta utilizando un fichero como fuente para crear el arbol, pero … xq no crear un modulo aparte que se encargue de gestionar la fuente de lectura??. Simplemente tendria que hacer que dado un nombre gestionar el modo de lectura de esa fuente. En nuestro caso seria abrir fichero, leer una linea y cerrar el fichero; pero si en vez de un fichero se decide q los arboles se carguen a traves de una conexion a un servidor deberia crear la conexion, leer una linea de la conexion, cerrar la conexion. Todo seguiria funcionando simplemente sustituyendo el modulo de carga. Esto supone un gran punto de portabilidad y escalabilidad/desarrollo, ademas de modular aun mas el “motor”. ASi que cree el ultimo modulo cuya interfaz fuese la de abrir un fichero, leer una linea, cerrar un fichero.

Y por ultimo decidi que en vez de manejar ficheros todo el rato manejara memoria por lo que proyecte el fichero en memororia y sustitui las llamadas de la interfaz por: Cargar Memoria, Leer Linea y Descargar Memoria.

Como es logico, habria que crear otra estructura para que este modulo supiera su estado, por lo que cree la ultima estructura necesaria:

FICHERO
Inicio
Puntero
Tamaño


En el caso de que la fuente sea la proyeccion de un fichero en memoria el tipo contenia la informacion anterior q sinificaba:

  • Inicio —>puntero posicion de memoria de inicio de la proyeccion.
  • Puntero –>puntero con la posicion de lectura.
  • Tamaño —>tamaño del fichero proyectado.


Con lo que la jerarquia de modulos ya estaria completada con el siguiente esquema:

ESQUEMA DE LA JERARQUIA DE MODULOS

Falta decir con que valor se inicializan las susodichas estructuras. Empezaremos por la rama de INTERFAZ, FICHERO y MENU.

  1. INTERFAZ: las estructuras que hay q inicializar son el historial, la linea, el prompt y el cursor. El historial se inicializa vacio (ya q no hay valor anterior de historias) cuando arranca la aplicacion. Para inicializar la linea crearemos una palabra vacia (unicamente contendra el caracter '\0' y a insertaremos en linea, justo despues haremos que Cursor apunte a la palabra vacia. Por ultimo Prompt se inicializa con el valor obtenido por PRINCIPAL (mas adelante explicaremos en detalle).
  1. FICHERO: hay que proyectar en memoria el fichero obtenido por PRINCIPAL, usaremos las direcciones de la proyeccion para rellenar el inicio, el fin y el tamaño.
  1. MENU: se inicializara la estructura leyendo el fichero menu y ayuda obtenido por PRINCIPAL.


En ultimo lugar falta mencionar un detalle. El modulo MENU necesita saber de las palabras introducidas en la linea, para conseguir esto hay un problemilla conceptual que es que MENU no sabe manejar la estructura de INTERFAZ. La forma de resolverlo es que el valor que devuelve la funcion de LEER_INTERFAZ (la cual espera por un caracter externo) sea ademas de el caracter externo una lista de tira de caracteres (char**). Esto supone que previamente han debido transformar los datos de la estrucutra de Linea (la del Prompt no hace falta) de INTERFAZ al tipo mencionado. Lo mas logico pensar es que INTERFAZ se encargue de esta labor (al conocer su estructura) y esta la realice unicamente antes de finalizar la llamada LEER_INTERFAZ. Pero el encargado de destruir dicha estructura sera PRINCIPAL, ya que en cada iteracion del bucle llamara a la funcion, recibira los parametros, ejecutara las funcioens necesarias, los liberara y volvera a empezar el bucle, volviendo a usar el mismo puntero para guardar los parametros.

Que PRINCIPAL se encargue de realizar esta labor no añade mas funfiones/finalidades a este modulo, ya que PRINCIPAL se encarga de la sincronizacion de ambos procesos y el paso de parametros entra dentro de sus responsabilidades.

Funcionalidad

CONFIGURACION
Este es el ultimo aspecto que se ha tenido en cuenta a la hora e realizar el diseño de la aplicacion. Este apartado intenta dar una mayor riqueza a la aplicacion dotandola de una mayor configurabilidad y que esta se pueda realizar de forma sencilla y clara. Su objetivo es que no tengamos que modificar ningun modulo salvo el de configuracion.

En primer lugar agrupe todas las variables globales, constantes, … en un unico modulo compartido por el resto de modulos. Ya que sera este el que tenga todas las variables que puedan modificar o ajustar el “motor”. A este modulo lo he llamado “CONSTANTES”. En el se encuentran diversos valores necesarios para el ajuste de la herramienta desde la longitud maxima que puede tener una linea del fichero menu y ayuda (necesarios para la creacion del menu), hasta el caracter especial utilizado para la palabra magica y la palabra magica. De este modo tenemos localizados los parametros que ajustan el funcionamiento global y no tendriamos que modificar en ningun momento el resto de modulos.

Pero aun se puede facilitar modulando mas el diseño, ya que en el caso de que se quiera añadir una nueva funcionalidad habria que modificar INTERFAZ para hacer que detectara el nuevo caracter, y en el caso de de que sea un caracter interno tambien habria que modificarlo para añadir la funcionalidad que requeramos, en el caso de ser externa habra q modificar el modulo MENU. Como se puede observar con esta version el objetivo de relacionar caracteres con funciones es un poco tosco ya que lo hariamos nosotros, no la maquina; aunq si es cierto que esta muy preparado el codigo para hacerlo (una entrada mas en un case).

Para solventar este escoyo y automatizar el proceso de añadido de funcionalidad deberiamos realizar dos mejoras:

 1º Que se encargue el modulo CONSTANTES de clasificar los distintos caracteres por medio de una macro. De este modo para añadir 
    modificar los caracteres tendriamos unicamente que modificar las macros de CONSTANTES, ya que INTERFAZ usara dichas macros para 
    decidir que hacer.\\
 
 2º Se debe de separar las funciones internas y externas de los modulo INTERFAZ y MENU, xa asi evitar tener que modificar INTERFAZ y 
    MENU. Estos modulos tienen la funcionalidad para gestionar las operaciones basicas sobre las cuales se sostiene la aplicacion. Para 
    añadir funcionalidad no hay que modificar la funcionalidad basica, asi conseguimos una mayor robustez de la aplicacion. Pero tambien 
    habra se requeriria crear unas pequeñas estructuras que guarden la informacion necesaria para relacionar las distintas funciones con 
    los caracteres.\\

Si aplicamos estas mejoras tendriamos dos modulos (uno para internas y otro para externas) en el que tenemos que crear la funcion que deseamos que se ejecute y en el modulo CONSTANTES tendriamos que añadir el caracter y modificar las estructuras (simples) para que la relacionen con la funcion deseada que hemos añadido. Entonces unicamente para modificar la funcionalidad de la herramienta unicamente tenemos que “tocar” estos 3 modulos, los otros permaneceran “intactos”, garantizando que la base del programa sigue funcionando correctamente. Si algo corrompe el sistema es la funcion.

NOTA: En el ejecutable entregado no estan realizadas estas mejoras, aunque en la ultima version del codigo si esta realizada. Hay 3 modulos:\\
          *CONSTANTES que define y relaciona las teclas con las funciones.
          *FUNC_INTERFAZ que contiene las funciones que editan la linea de comandos, es decir las q atacan a INTERFAZ
          *FUNC_MENU que contiene las funciones que modifican o consultan el arbol del MENU
      El motivo de no entregarlas es la falta de tiempo para poder testearlas. Se saben q funciona ya que se ha comprobado, pero también 
      se sabe de la existencia de bugs y que no estan bien liberadas las estructuras



Situacion Final

Ya se tiene el diseño “final” de como es la aplicacion. Con el fin de clarificar y asentar las ideas presentadas mostraremos el esquema de la jerarquia de modulos (aplicando las mejorar para aumentar la configuracion); seguidamente lo superpondremos con los posibles caminos de flujos de datos.


ESQUEMA jerarquia de modulos (mejorado)



ESQUEMA SUPERPUESTO


Para finalizar este capitulo falta decir que esta aplicacion no puede emular por si sola el comportamiento de CIT, mas concretamente de su menu navegable. Esto es debido a que el menu de CIT no tiene ningun tipo de patron, despues de un nodo menu puro, puede haber un nodo menu especial y luego otro menu puro. Para poder abordar este menu tome la decision de que habria que cortar por algun lugar, asi que estudiando de nuevo todos los menus navegable encontre un “patron” sobre la jerarquia de los nodos del menu. Este era el representad por el siguiente patron.

 [(Menu puro)* AND (Menu hibrido variable + menu hibrido no variable)* AND (script)]*


Como se refleja en el esquema es un patron repetido n veces. Asi que la decision que tome es que el “motor” solo reconociera arboles cuyo patron fuese el mencionado, pero no se pudiera repetir. Lo que haria en el caso de repetir seria crear otro proceso con el mismo motor pero con el subarbol de dicho nodo.

Entonces si se quiere reproducir una topologia con dicho patron pero que en alguna de sus ramas se vuelve a repetir el patron, lo que se hara es generar el primer arbol y solo cuando se solicite navegar por la rama que repite el patron se creara otro proceso que volvera a ejecutar la herramienta (ej: desde un bash ejecutas otro bash) pero en este caso el arbol sera el surarbol de la rama y en el momento que retroceda (es decir vuelva al padre del nodo raiz de esa rama) se cerrara el segundo proceso y seguira ejecutando el primer proceso por el donde se quedo. Es igual que si hacemos un ssh A y desde A hacemos un ssh B, cuando cerremos B estaremos en A.

Tambien es una forma de modular, en vez de tener un unico arbol grande, podemos tener 5 arboles pequeños. Esta nueva “propiedad” sirve para facilitar la modularidad entre diversos modelos de la version del mapa que tienen, x ejemoplo si tengo 3 modelos que tienen un arbol comun y luego cada uno tiene diversas ramas simplemente tendria que hacer que cada menu tenga el mismo fichero con la topologia comun y otro fichero con la topologia del arbol especializada.

Otro punto a su favor es que no se tendra que tener toda la informacion del arbol en memoria, solo estara cuando accedamos a los subarboles, en tal caso tendriamos cargado la informacion del arbol anterior y la de los subarboles. Despues la liberariamos.

Pero funcionar asi supone una serie de problemas, los cuales muestro a continuacion:

 1º Saber que fichero contiene el arbol que deseamos cargar, tanto la topologia como la ayuda.\\
 2º Cuando ejecutamos nuevamente la herramienta con un subarbol debemos modificar o manter el prompt de la linea. Asi que deberemos
    necesitamos una forma para inicializarla con un valor que debemos determinar.\\
 3º El tercer problema se debe a una peculiaridad de los nodos hibridos de CIT, los cuales para avanzar por ellos necesitan darles un 
    parametro (ej: una interfaz, una mascara, una direccion,...) que se ira "acumulando" xa q cuando se ejecute el nodo ejecutable se le 
    pasen como argumentos. Entonces con este modo de funcionamiento si ejecutamos un nodo ejecutable de un subarbol habría que pasarle 
    los parametros "acumulados" de los arboles anteriores, mas los suyos propios. Por este motivo hay un problema de comunicacion entre 
    las distintas "instanticas" de la herramienta.\\


Las soluciones que he tomado para solventar estos problemas han sido las siguientes:

 1º Hay que resolver dos cuestiones: uno donde y como hubicarlos, y dos como transmitir el nombre del fichero que contiene la 
    informacion del arbol (fichero topologia y ayuda). La forma mas sencilla para resolver la primera cuestion es crear una subcarpeta 
    en donde este ubicado el ejecutable de la herramienta con el nombre "arboles" e incluir en ella todos los ficheros de topologias que 
    existan y otra subcarpeta, llamada "ayuda", con el mismo numero de fichero y mismo nombres que la carpeta de "arboles" en la que se 
    alojaran todos los ficheros que tengan la informnacion de ayuda del arbol. Para resolver la seegunda cuestion me decante por el uso 
    de variables de entornos, ej: si queremos arrancar la maquina con un arbol del menu concreto deberemos crear la variable de entorno 
    correcta y darle el valor del NOMBRE del fichero; en caso de no existir la variable de entorno el programa abrira el arbol cuyo 
    nombre lo indica el modulo CONSTANTES. Con esta solucion para hacer que se vuelva a ejecutar el "motor" pero con otro arbol distinto 
    bastaria con crear un nodo hoja distinto "ejecutable interno" que lo unico que haga sea ejecutar un script, el cual establecera 
    variable de entorno apropiada con el valor que queramos y ejecutara de nuevo la aplicacion (esta vez se cargara el nuevo arbol).\\
    NOTA: el motivo de crear un nuevo tipo de nodo (ahora habran 4, 2 menus y 2 ejecutables) es para facilitar la implementacion, ya que 
          conceptualmente los dos nodos ejecutables son iguales, por lo que no haria falta crear uno nuevo.\\


 2º Bueno pues solo tenemos que facilitar otra forma de pasarle un valor a una instancia de la aplicacion xa que esta pueda inicializar 
    el prompt con dicho valor. Y esto lo he hexo con el mismo sistema que en el apartado anterior una variable de entorno (PROMPT). En 
    el caso de no existir se tomara por defecto valores establecidos en el modulo CONSTANTES. Ahora cuando hay que crear otro subarbol 
    se ejecutara un nodo hoja interno, el cual solo tendra que establecer la variable de entrono para el fichero del nombre del menu, el 
    valor para inicializar el PROMPT y ejecutar otra instancia de la herramienta.\\


 3º Una vez mas exare mano de las variables de entorno para solventar la comunicacion entre varios procesos que ejecutan la aplicacion. 
    En este caso necesitare primero de una variable de entorno que indique el numero de parametros y luego una variable de entorno por 
    cada parametro. De esta forma el nodo ejecutable podra saber cuantos y cuales son los parametros. Un  ejemplo de este caso es:\\
    NUM_PARAM = 3 (num de parametros).\\
    PARAM1 = el valor del 1º parametro
    PARAM2 = el valor del 2º parametro
    PARAM3 = el valor del 3º parametro


Siguiendo con la politica de organizacion en el sistema de ficheros para mejorar y aumentar la portabilidad decidi realizar un cambio mas. Como hay 3 tipos distintos de scripts segun su funcionalidad, estos se organizaran en el sistema de ficheros, es decir, crearemos una carpeta mas, la cual se llamara “scripts”. Dentro de esta crearemso 3 subcarpetas, una por cada uno de los distintos tipos. Quedando un directorio script, y dentro de esta carpeta habran otras tres con nombre internos, externos, cmp_parametros (el cual simplemente se limita a compara parametros) respectivamente.

Todos los scripts que se requieran deberan de crearse.

Todas las direcciones o rutas que se necesitan para abrir un fichero o directorio seran “guardados” en variables globales. De este modo todos los que necesiten ejecutar o abrir un fichero simplemente debera concatenar la direccion de la var global determinada a el nombre. El encargado de dar valor a estas variables sera el modulo PRINCIPAL, encargandose finalmente de la inicializacion, sincronizacion y obtencion de variables de entorno.

Compilacion Cruzada

Para compilar cruzado las librerias ncurses se deber ejecutar los siguientes mandatos:
./configure –with-shared –enable-overwrite –host=powerpc-linux –prefix=“ruta del SFF que se telecargara al router de nuetra maquina”
make
make install.libs
make install.progs

Estos mandatos debermos ejecutarlos con permisos de superadministrador, y deberemos de asegurarnos que el ejecutable gcc-powerpc-linux esta en el PATH de superusuario. Por este motivo aconsejo que previamente se ejecuten los comandos:

sudo su
export PATH= $PATH:/“ruta del ejecutable”

Ya estan compiladas e instaladas la libreria ncurses, ahora hace falta compilar el ejecutable. Al tener que compilarlo dinamicamente tambien deberemos tener en nuestra maquina las librerias de powerpc.

Asi que deberan compilarse como se ha indicado anteriormente las librerias en una ruta de de nuetro sistema de ficheros, sin llegar a sobrescribir las librerias para nuestra maquina. Una vez tengamos instalada la libreria ncurses para powerpc en nuestra maquina, solo nos queda compilar cruzado el ejecutable enlazandolo a dicha libreria, recordad que necesitaremos tener permisos de superusuario para poder escribir en SFF que se telecargar y debera estar el compilador gcc-powerpc-linux en el path. El mandato seria:

powerpc-linux-gcc -Wall -g /"^"/lib/libncurses.so -I /"^"/include/ *.c -o "ruta del SFF"
^=="ruta de instalacion de ncurses para powerpc-linux"




NOTA: Hemos preferido instalar la version 5.7 manualmente antes de instalar la version 5.6 que viene integrada en buildroot, ya que 
ademas de corregir multitud de bug's se ha incorporado el soporte de threads 
 
proyectos/teldatsi/menu.txt · Última modificación: 2012/10/08 17:58 (editor externo)
 
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki