sisoputnfrba / foro

Foro de consultas para el trabajo práctico
147 stars 6 forks source link

Duda con FUSE #425

Closed juanoubi6 closed 7 years ago

juanoubi6 commented 8 years ago

Buenas, queria plantear una duda que tengo con el funcionamiento del proceso que va a correr con el FUSE y de FUSE en general: vos a ese proceso, al correrlo, le vas a tener que pasar un path que va a terminar siendo el punto de montaje. Cualquier peticion que vos hagas sobre un archivo que se encuentre en ese punto de montaje (y que esa peticion este definida en la estructura fuse_operations, como por ejemplo un read) va a ser manejada por el proceso corriendo fuse, y va a ejecutar la funcion que tengas asignada a esa peticion.

Ejemplo de como creo que funciona: voy a asociar en el proceso que corre fuse el punto de montaje /home/prueba que contiene el archivo test.txt. En fuse_operation asigno .read = MiLectura , y lo que hace la funcion MiLectura es un printf de "hola". Entonces, cuando cualquier proceso quiere leer el archivo test.txt, el proceso con fuse maneja esa peticion de lectura (porque el archivo esta en el punto de montaje que defini) y muestra por pantalla "hola".

Esto es asi o nada que ver?. Ademas, queria preguntar como puedo "testear" el ejemplo que nos pasaron. Llegue hasta correr el ejemplo y pasarle el punto de montaje (una carpeta vacia) y me creo dentro de esa carpeta el archivo "hello". Ahora queria leer ese archivo y que me diga la el tamaño (que es lo que supuestamente hace) pero no estoy pudiendo.

mgarciaisaia commented 8 years ago

¡Aloha!

En principio, agarraste bastante bien la idea. De hecho, la única corrección que se me ocurre hacerle a ese primer párrafo es que el directorio punto de montaje tiene que estar vacío:

utnso@utnso40:~/so-fuse_example$ ll vacio/
total 8
drwxrwxr-x 2 utnso utnso 4096 sep  4 01:58 ./
drwxrwxr-x 5 utnso utnso 4096 sep  4 01:56 ../
-rw-rw-r-- 1 utnso utnso    0 sep  4 01:58 sarasa
utnso@utnso40:~/so-fuse_example$ ./hello-fuse vacio/
fuse: mountpoint is not empty
fuse: if you are sure this is safe, use the 'nonempty' mount option

La idea de que el punto de montaje esté vacío viene porque, justamente, la idea es que en ese directorio "se vean" los contenidos de tu filesystem. Si ese directorio no estuviera vacío antes de montar tu FS, el contenido de ese directorio estaría en el FS que contiene tu directorio (digamos, el ext4 con que está formateada la partición root de la VM). Al montarlo, ¿qué harías? ¿Mostrás el contenido del FS nativo y el de tu FS? ¿Mostrás sólo uno de los dos? Si mostrás los dos, ¿en cuál de los dos FS guardarías los archivos nuevos que se creen? ¿Y si hay dos archivos con mismo nombre en los dos FS?

Nada, por sanidad, mount requiere que el directorio en que se monta un filesystem esté vacío, y FUSE mantiene esa restricción (aunque nos permite obviarla pasando ese flag que nombra en el comando que mostré).

Entonces, en tu ejemplo, /home/prueba tiene que estar vacío. Si existiera /home/prueba/test.txt, eso sería porque test.txt existe en la raíz de tu filesystem.

Para el sistema operativo, que test.txt exista en la raíz de tu filesystem significa que tu función para .readdir, cuando le pasan como parámetro el path /, "contesta" que existe un archivo llamado test.txt (y por "contestar" me refiero a "usás la función chistoza esa fuse_fill_dir_t como FUSE lo pide).

Lo importante es eso: ni a FUSE ni al SO le interesa cómo/si almacenás tus archivos o no. Si tu programa le dice que existe un archivo, el SO te va a creer. Cuando pida el contenido, tu programa le dice cuál es el contenido del archivo - si está almacenado, si está inventando, o si está bajándolo de internet, al SO no le importa. FUSE cree en lo que digan tus funciones, porque, bien UNIX: el programador sabe lo que hace.


En cuanto al ejemplo, ¿por qué es que no estás pudiendo testearlo? ¿Qué es lo que estás intentando, y por qué no sale?

Una forma sencilla de leer un archivo es el programa cat, que imprime todo el contenido del archivo en pantalla. En este caso, podrías probar de hacer cat tmp/hello (asumiendo que montaste el FS en un directorio tmp/), y con eso vas a ver el Hello, world! que la función hello_read devuelve.

Este es un caso de FS que no almacena los datos que muestra. Están hardcodeados - las respuestas son parte del binario.

Otro filesystem "parecido" (en cuanto a esto de no almacenar información que más tarde puede ser recuperada, si no que muestra datos que no están guardados en ningún lado) es el procfs, que se monta por defecto en los Linux en /proc. Ese filesystem permite inspeccionar todos los procesos que están corriendo en el sistema, exponiendo como directorios y archivos distinta información sobre ellos. Pegale una revisada, y, si querés, jugá dos minutos con diseñar muuuuuuuuy por encima cómo diseñarías un FS así.

¡Un abrazo!

juanoubi6 commented 8 years ago

Te hago una pregunta con respecto a este parrafo Lo importante es eso: ni a FUSE ni al SO le interesa cómo/si almacenás tus archivos o no. Si tu programa le dice que existe un archivo, el SO te va a creer. Cuando pida el contenido, tu programa le dice cuál es el contenido del archivo - si está almacenado, si está inventando, o si está bajándolo de internet, al SO no le importa. FUSE cree en lo que digan tus funciones, porque, bien UNIX: el programador sabe lo que hace.

Como es eso de que "el SO te va a creer"?. Lei algo de que mis funciones siempre debian devolver un numero positivo para decir "esta todo ok" o un negativo para "salio todo mal". A esto te referis? Independientemente de lo que hagan mis funciones que manejan read u open, siempre tengo que devolver algo para indicarle al SO lo que paso?

Todavia no me termina de cerrar la idea de que en un directorio vacio se va a crear un filesystem. Cuando despues yo tenga el archivo binario que represente al OSADA, como relaciono eso con el cliente que usa fuse? Seria algo asi como que las peticiones se le hacen al cliente que usa fuse: supongamos que un mapa ejecuta una linea int leidos = read (ArchivoOsada,buffer,50,0), como mi ArchivoOsada se encuentra dentro de mi punto de montaje, esa lectura es "captada" por el cliente que usa fuse y este cliente la maneja como el quiere. Lo que retorne mi funcion read es lo que va a terminar quedando en la variable leidos. La pregunta a todo esto es, el OSADA va a tener que estar dentro del punto de montaje si o si, como se maneja eso??

Con respecto a lo de testearlo, ya probe el ejemplo que da la catedra como vos decis. El tema es que yo pense que si le cambiabas la funcion definida a read en fuse_operation y le asignabas una funcion que diga "Hola mundo", cada vez que yo quiera leer un archvio de ese punto de montaje me iba a aparecer por consola "Hola mundo", y no el contenido que tenga el archivo. Justamente eso queria probar yo, que de alguna manera el SO ejecute una syscall "read" sobre ese punto de montaje y fuse lo maneje como yo le dije que lo maneje (mostrando "Hola mundo").

Muchas gracias!

mgarciaisaia commented 8 years ago

Venís re afilado con lo que venís entendiendo... ¡y todavía no pasaste por la charla de FUSE del 17! 👏👏

Trato de ir por partes, para mezclarnos lo menos posible:

Como es eso de que "el SO te va a creer"?. Lei algo de que mis funciones siempre debian devolver un numero positivo para decir "esta todo ok" o un negativo para "salio todo mal". A esto te referis? Independientemente de lo que hagan mis funciones que manejan read u open, siempre tengo que devolver algo para indicarle al SO lo que paso?

Veamoslo de este modo: un disco rígido no almacena archivos ni directorios, si no bloques de datos. Los que saben interpretar los bloques de datos que conforman una partición de disco para entender qué archivos y directorios hay representados ahí son los filesystems.

En general, los sistemas operativos vienen con un algunos filesystems ya implementados "en el kernel". En particular, el kernel de Linux tiene en algún lado algún módulo que sabe entender ext4, otro que sabe entender FAT32, y así con los distintos filesystems.

El kernel en sí no sabe un filesystem en particular: el kernel sabe las generalidades de interacción con archivos (decime qué hay en un directorio, decime los atributos de este archivo, creame un archivo, dame el contenido de los bytes 3316 a 4012 de este archivo, etc). Los que saben cómo atender cada una de esas solicitudes son los filesystems. Entonces, para el kernel es "lo mismo" pedirle los bytes 3316 a 4012 de "hola.txt" a EXT4 que a FAT32. La única diferencia es quién va a atender este pedido (un módulo u otro), y cómo este lo va a resolver - pero el kernel va a esperar una respuesta con un formato único de ambos dos.

Si cursaste Paradigmas, Diseño, o simplemente sabés programar en objetos y eso, esto huele zarpado a polimorfismo, encapsulamiento, delegación y todas esas yerbas: el kernel pide las cosas de una manera, y espera la respuesta de una manera, y confía en que el receptor de ese pedido hará las cosas bien y le dará la respuesta apropiada. Así, el kernel no va a "validar" que lo que el módulo de EXT4 o FAT32 (o el FS que sea) le haya contestado sea cierto... porque tampoco tiene sentido pensar en que el kernel "sabe" si es cierto o no.

Y lo mismo pasa, a través de FUSE, con tu filesystem.

El kernel, claro está, no "sabe" OSADA. Vamos, hasta hace dos semanas, OSADA no existía. ¿Por qué el kernel de Linux entendería OSADA? ¿Por qué un finlandés que se baje la última versión de Ubuntu querría que su computadora entienda OSADA, siendo un filesystem tan de juguete?

Pero vos (tu grupo, je) vas a implementar un filesystem, porque vos sí tenés interés en que la VM del labo entienda OSADA - lo necesitás para aprobar el TP, básicamente :)

¿Y cómo lo lográs? Bueno, con FUSE aprovechás ese "polimorfismo" del que hablaba más arriba: si hacés un programa que cumpla con el formato que te pide FUSE, él se encarga de que el kernel "compre" lo que vos le vendés. El kernel le pregunta a FUSE por las operaciones que se hacen sobre el punto de montaje de tu filesystem, FUSE le pregunta a tu programa, y lo que tu programa conteste será lo que el SO diga que hay en tu filesystem.

A esto iba con lo de "el SO te va a creer". Del mismo modo que el SO confía en lo que el módulo de FAT32 dice que hay en tu pendrive, de ese mismo modo le va a creer a tu proceso filesystem cuando alguien haga operaciones sobre el punto de montaje de tu FS.

Esto que me está llevando mil párrafos explicar es la clave para entender FUSE y los filesystems: el kernel confía en los filesystems, porque así está diseñado, y así funciona siempre. En tu disco rígido no hay archivos: hay un montón de bytes ordenados de forma tal que un programa hecho para seguir la especificación del filesystem con que esté formateado tu partición pueda interpretar esos bytes como archivos y directorios y permisos y fechas de modificación y todas esas cosas.

Si no terminás de entender la idea, no te odies, tampoco: captar este concepto es el ahá moment, es el 50% del laburo que requiere poder hacer el FS del TP. Date tiempo, las ideas van a ir madurándote con él.

Todavia no me termina de cerrar la idea de que en un directorio vacio se va a crear un filesystem.

Esto es lo más loco de los filesystems: se anidan. Hay un filesystem "raíz" en tu sistema operativo (el que está en /), y el comando mount (y la syscall correspondiente) sirven para "anexar" otro filesystem en algún directorio vacío del principal. De otro modo, no podrías referenciar a ese otro filesystem, porque "no tiene dirección" (path, en este caso).

Cada filesystem tiene su propio directorio raíz, y el sistema operativo tiene un filesystem al que considera raíz. El directorio raíz del filesystem raíz será el que se ve en /. Después, con mount se van a montar otros filesystems sobre el anterior, eligiendo los puntos de montaje. A partir de que se monta un filesystem, en el punto de montaje ya no se va a ver el contenido que había en el FS original, si no que ahí se verá el contenido del directorio raíz de ese otro FS.

El directorio vacío en que se monta el FS es más que nada "una excusa" - es la forma que tiene el kernel de darle un path en el que acceder a la raíz del FS que se está montando.

Quizá confunda más de lo que aclare, pero pegale una mirada al archivo /etc/mtab de la VM para ver qué filesystems hay montados en ese momento en la VM. La "segunda columna" de ese archivo lista los puntos de montaje de cada FS. Capaz ayuda a verlo mejor, no se.

Cuando despues yo tenga el archivo binario que represente al OSADA, como relaciono eso con el cliente que usa fuse? Seria algo asi como que las peticiones se le hacen al cliente que usa fuse: supongamos que un mapa ejecuta una linea int leidos = read (ArchivoOsada,buffer,50,0), como mi ArchivoOsada se encuentra dentro de mi punto de montaje, esa lectura es "captada" por el cliente que usa fuse y este cliente la maneja como el quiere. Lo que retorne mi funcion read es lo que va a terminar quedando en la variable leidos.

Exactamente. Bah, cuando hablamos de "lo que retorne mi función read" no es exactamente lo que tu read haga return. Como dice la documentación del hello_read en el ejemplo, el contenido del fragmento del archivo que te piden leer tenés que almacenarlo en la memoria apuntada por buf (uno de los parámetros que recibe tu función), y tenés que hacer return de un 0 si todo anduvo bien, o de algún código de error si hubo algún problema.

Pero, salvando eso, sí: lo que tu función guarde en ese buf, eso es lo que el SO le va a devolver a quien haya hecho el read.

La pregunta a todo esto es, el OSADA va a tener que estar dentro del punto de montaje si o si, como se maneja eso??

Acá un poco te perdí, por ambigüedad y sobrecarga de nombres: ¿a qué te referís con "el OSADA" que va a tener que estar en el punto de montaje?

Con respecto a lo de testearlo, ya probe el ejemplo que da la catedra como vos decis. El tema es que yo pense que si le cambiabas la funcion definida a read en fuse_operation y le asignabas una funcion que diga "Hola mundo", cada vez que yo quiera leer un archvio de ese punto de montaje me iba a aparecer por consola "Hola mundo", y no el contenido que tenga el archivo. Justamente eso queria probar yo, que de alguna manera el SO ejecute una syscall "read" sobre ese punto de montaje y fuse lo maneje como yo le dije que lo maneje (mostrando "Hola mundo").

Lo que entendés es correcto - y así funciona. Lo que probablemente haya pasado es que lo hayas implementado mal, y por eso no funcionó, y por eso tiraste abajo tu teoría :)

La forma más sencilla de probar esto es cambiar la constante que define el contenido del archivo, recompilar el FS, volver a montarlo, y ver los cambios.

Quizá ya habías hecho eso, y te olvidaste de la parte de recompilar.. O volver a montar.. O andá a saber.

La otra opción es que hayas hecho que tu función de read hiciera return "HOLA MUNDO";, y eso, como te decía un poco más arriba, no es el formato que FUSE espera.

Confío en que con esto puedas revisar más o menos qué es lo que pasó, e incluso quizá solucionarlo. Si no lo lográs, mostrame un poco mejor el código que cambiaste, y vemos por qué no está funcionando.

Pero es un tema de implementación esto - la idea está joya.

¡Un abrazo, che!

juanoubi6 commented 8 years ago

Osea que, el retorno de mis funciones es solo para decir si salio todo bien o no. A la lectura/escritura/apertura/ect que realize mi proceso mapa sobre una ruta/path que se encuentre en el punto de montaje le va a llegar lo que yo ponga en el buffer de mis funciones (no me interesa como lo hace, pero lo hace). Yo podria asociar a la funcion .read del pokedexCliente una funcion que llene el buffer con "123456789".

Entonces, si un mapa trata de leer un archivo cualquiera (que se encuentre dentro del punto de montaje) mediante mi cliente con fuse, se va a llenar el buffer de esa funcion con "123456789". Cualquier mapa que utilize mi cliente para leer un archivo se va a encontrar con que va a obtener un "123456789" como respuesta. Esto es asi?

La forma más sencilla de probar esto es cambiar la constante que define el contenido del archivo, recompilar el FS, volver a montarlo, y ver los cambios. Quizá ya habías hecho eso, y te olvidaste de la parte de recompilar.. O volver a montar.. O andá a saber. La otra opción es que hayas hecho que tu función de read hiciera return "HOLA MUNDO";, y eso, como te decía un poco más arriba, no es el formato que FUSE espera.

No entendi para que son esas constantes que hay en el ejemplo. Vi que, al momento de ejecutar el ejemplo en un punto de montaje, se crea el archivo que vos definis por defecto con el contenido por defecto. Este archivo que se crea lo pude leer desde un proyectito aparte que me hice y ademas imprimi el contenido en pantalla. El tema es que yo, dentro de la funcion read del ejemplo de fuse, agregue la linea printf("Hola probando"). Entonces asumi que al ejecutar mi ejemplo de prueba para leer el archivo, en pantalla me iba a aparecer "Hola probando" y luego "Hola mundo"

Acá un poco te perdí, por ambigüedad y sobrecarga de nombres: ¿a qué te referís con "el OSADA" que va a tener que estar en el punto de montaje?

Con "el OSADA" me referia al archivo binario que se nos va a dar. Ese archivo va a tener que estar dentro del punto de montaje que yo le pase al cliente que utilize fuse? Lo va a tener que crear el cliente fuse de adentro asi como crea el archivo "Hello" del ejemplo?

mgarciaisaia commented 8 years ago

Osea que, el retorno de mis funciones es solo para decir si salio todo bien o no.

Bueno, para ser sinceros, esto depende de cada función. Es decir, es FUSE el que dice qué tiene que hacer cada una de tus funciones para que él las entienda. En general, las funciones devuelven si fallaron o no, sí - pero tampoco todas. Hay algunas que, por ejemplo, devuelven la cantidad de bytes leídos/escritos. Una vez más: FUSE es el que determina qué tiene que devolver cada una de las funciones que implementes, y qué más tiene que hacer cada una además de devolver algún valor.

A la lectura/escritura/apertura/ect que realize mi proceso mapa sobre una ruta/path que se encuentre en el punto de montaje le va a llegar lo que yo ponga en el buffer de mis funciones (no me interesa como lo hace, pero lo hace). Yo podria asociar a la funcion .read del pokedexCliente una funcion que llene el buffer con "123456789".

Entonces, si un mapa trata de leer un archivo cualquiera (que se encuentre dentro del punto de montaje) mediante mi cliente con fuse, se va a llenar el buffer de esa funcion con "123456789". Cualquier mapa que utilize mi cliente para leer un archivo se va a encontrar con que va a obtener un "123456789" como respuesta. Esto es asi?

Yeap, es así.

Ojo, en el ejemplo también está hardcodeada otra de las operaciones de FUSE: readdir. readdir es la función en que tenés que decirle a FUSE qué archivos existen dentro de un directorio determinado de tu FS.

El ejemplo que subimos dice que únicamente existe el directorio /, y que tiene un único archivo - entonces, si tu mapa intenta leer otro archivo que no sea ese, la operación va a fallar porque tu FS va a decirle que ese archivo no existe.

Pero, la idea principal está bien: si tu FS dijera que existe cualquier archivo que le preguntan, podría contestar siempre que el contenido de todos los archivos es "123456789" haciendo eso que decís 👍

No entendi para que son esas constantes que hay en el ejemplo.

Cada una de esas constantes es lo que su comentario dicen que son. Entendiste bien, pero la prueba no estuvo tan bien, me parece. Seguí leyendo...

Vi que, al momento de ejecutar el ejemplo en un punto de montaje, se crea el archivo que vos definis por defecto con el contenido por defecto.

Ojo acá, eh. Lo de "al ejecutar se crea el archivo que vos definís" probablemente indique estar confundiendo los tantos. El archivo que está definido en el código no "se crea" - existe. Tu FS contiene ese único archivo, siempre. ¿Que no está almacenado en ningún lado? Compro, ponele - pero eso no significa que no exista.

Cuando vos ponés a correr tu proceso, FUSE monta tu FS en el punto de montaje. Entonces, a partir de ese momento, ahí donde se veía un directorio vacío ahora se puede ver el contenido de otro FS. Y ese FS contiene un único archivo, que es ese que estás viendo - que está hardcodeado en el FS. Pero no se crea ese archivo - simplemente ahora se lo puede acceder, porque se montó el FS.

Por así decirlo, ni tu EXT4 ni tu disco rígido conocen a ese archivo. No se modificó el FS de tu VM para que ahora incluya un archivo más - simplemente se le montó otro FS, que traía ese archivo dentro.

Puede parecer una huevada esta diferencia, pero es la clave "conceptual" de la que vengo hablando en este thread. También puede parecer un bardo, pero, meh - una vez más, te estás adelantando a la charla de FUSE, date tiempo.

Este archivo que se crea lo pude leer desde un proyectito aparte que me hice y ademas imprimi el contenido en pantalla.

No te olvides que esto... es un FS. Podés hacerte un proyecto aparte que lea y blah, pero también podés correr el FS, y ejecutar desde otra consola cat /path/al/punto/de/montaje/hello para ver el contenido del archivo - como harías con cualquier otro archivo desde la consola.

El tema es que yo, dentro de la funcion read del ejemplo de fuse, agregue la linea printf("Hola probando"). Entonces asumi que al ejecutar mi ejemplo de prueba para leer el archivo, en pantalla me iba a aparecer "Hola probando" y luego "Hola mundo"

Vas bien apuntado, pero acá estás mezclando dos "flujos de datos" distintos.

Cuando ejecutás un printf() imprimís texto en la salida estándar de tu proceso. Hay algunas cosas interesantes para hablar sobre eso, pero será en otro momento - si salida estándar no te dice nada, pensemoslo como la pantalla. Entonces, printf() escribe en pantalla.

Pero, fijate lo que hace hello_read como la subimos nosotros: no imprime el contenido de DEFAULT_FILE_CONTENT (el que dijimos que es el contenido hardcodeado del único archivo hardcodeado de nuestro FS), si no que hace un memcpy de esto (teniendo en cuenta offsets y eso, que por ahora podemos ignorar) en el buf que la función recibió por parámetro.

Y esto, ¿por qué? Porque FUSE dice. Porque esa es la especificación de FUSE: si usted quiere hacer un FS en el que se puedan leer archivos, yo le llamo esta función con un path, un buffer, un offset y una longitud, y usted me graba los bytes correspondientes en ese buffer y me devuelve la cantidad de bytes que me grabó ahí.

¿Y si tu función hace printf()? A FUSE le importa poco y nada, sobretodo porque ni siquiera se entera de que se imprimieron bytes en pantalla.

¿Se entiende por qué no funcionó la prueba?

Con "el OSADA" me referia al archivo binario que se nos va a dar. Ese archivo va a tener que estar dentro del punto de montaje que yo le pase al cliente que utilize fuse? Lo va a tener que crear el cliente fuse de adentro asi como crea el archivo "Hello" del ejemplo?

Gotcha.

El archivo binario que nombrás viene a representar un disco rígido. Como sería un tanto engorroso pedirles que formateen con OSADA una partición del disco virtual que usa la VM, preferimos almacenar todos los datos y metadatos del FS en un archivo. Es decir, en lugar de usar una partición de disco para almacenar nuestro FS, usamos un archivo que representa a una partición.

En ese archivo (al que, a partir de ahora, llamaré "el disco") vas a almacenar tanto los bloques de datos de los archivos que haya que almacenar en tu FS (por ejemplo, el archivo de metadata de un Entrenador, la medalla de un gimnasio, o la intro de Pokemon en mp3 que tengamos ganas de grabar durante las pruebas) como la metadata que OSADA especifica en el Anexo I del TP - en formato OSADA.

A eso nos referimos cuando decimos que una partición está formateada con un FS en particular: en este caso, tu disco va a estar formateado con formato OSADA - es decir, va a almacenar los datos y estructuras administrativas de la manera que la especificación de OSADA (Anexo I) indica.

Pero ese disco no va a estar guardado en el punto de montaje, como decís. De hecho, lo charlamos acá: el punto de montaje es un directorio que tiene que estar vacío.

Pero hay relación entre el punto de montaje y tu disco: a partir del momento en que lo montes, tu FS va a colaborar con FUSE y el kernel para que en el punto de montaje se pueda ver el contenido del directorio raíz que está almacenado en tu disco. 😵💥💥 ¡Mindblown!

Si tenemos en cuenta el temita de la red (que el Pokedex se divide en Cliente y Servidor), el Cliente va a correr en la máquina en que se monta el FS - es decir, la máquina que contiene el punto de montaje -, y el Server es el que necesita leer el disco e interpretarlo - así que el proceso Pokedex server va a tener que conocer, de algún modo, el path al archivo de disco, que va a estar almacenado en la máquina en que corre el Pokedex Server.

Altas preguntas, loco. Buena onda 👍

juanoubi6 commented 8 years ago

Muchas gracias!. Quedo todo muy claro, ya pude probar el ejemplo como queria (el tema era que en vez de imprimir en pantalla tenia que llenar el buffer de lo que se devuelve con lo que yo queria, ahora todos los archivos 'leen lo mismo').

Me surgio un tema a partir de lo ultimo que pusiste, porque yo lo habia pensado de otra manera

Pero hay relación entre el punto de montaje y tu disco: a partir del momento en que lo montes, tu FS va a colaborar con FUSE y el kernel para que en el punto de montaje se pueda ver el contenido del directorio raíz que está almacenado en tu disco. :dizzy_face::boom::boom: ¡Mindblown! Si tenemos en cuenta el temita de la red (que el Pokedex se divide en Cliente y Servidor), el Cliente va a correr en la máquina en que se monta el FS - es decir, la máquina que contiene el punto de montaje -, y el Server es el que necesita leer el disco e interpretarlo - así que el proceso Pokedex server va a tener que conocer, de algún modo, el path al archivo de disco, que va a estar almacenado en la máquina en que corre el Pokedex Server.

Yo lo habia pensado como que el cliente solo "captaba" las acciones de los otros procesos con respecto a temas de archivos, y le delegaba la responsabilidad al servidor de abrir "el disco" de OSADA y hacer lo que tenga que hacer con eso. Entonces el cliente era un mero pasamanos, que lo unico que tenia que hacer era ver lo que le llegaba del pokedex servidor y hacer con eso lo que hiciera falta (por ejemplo, si el servidor me leyo datos del disco y me los mandó, yo pongo esos datos en el buffer para que le lleguen a la funcion que invoco al fuse). Esto es asi o le pifie?

Otra duda que me surge (y a lo mejor mas tecnica) es el tema de los paths que recibe el cliente. Entiendo que los procesos siempre que pidan leer metadata necesitan un path de donde leerla, que debe ser relativo al punto de montaje. Supongamos que un proceso cualquiera va a querer buscar el archivo "file1" que se encuentra, por ejemplo, en /home/puntoMontaje/CarpetaEjemplo/file1.txt . Vos cuando ejecutes el proceso cliente con fuse (usando como punto de montaje el directorio puntoMontaje), esa carpeta va a tener que estar vacia. Entonces, si fuse trata de ubicar la carpeta "CarpetaEjemplo" no podria encontrarla, ya que el punto de montaje va a estar vacio. Entonces, los directorios va a haber que crearlos a mano para que el cliente con fuse detecte que "estan ahi" y pueda manejar esas peticiones? No se si se entendio

mgarciaisaia commented 8 years ago

Yo lo habia pensado como que el cliente solo "captaba" las acciones de los otros procesos con respecto a temas de archivos, y le delegaba la responsabilidad al servidor de abrir "el disco" de OSADA y hacer lo que tenga que hacer con eso. Entonces el cliente era un mero pasamanos, que lo unico que tenia que hacer era ver lo que le llegaba del pokedex servidor y hacer con eso lo que hiciera falta (por ejemplo, si el servidor me leyo datos del disco y me los mandó, yo pongo esos datos en el buffer para que le lleguen a la funcion que invoco al fuse). Esto es asi o le pifie?

Entiendo que lo que decís está perfecto. ¿Te parece que en algo se contradice con lo que escribí yo más arriba? Pregunto por lo de que lo habías pensado de otra manera - hasta acá parece que estamos en línea.

Otra duda que me surge (y a lo mejor mas tecnica) es el tema de los paths que recibe el cliente. Entiendo que los procesos siempre que pidan leer metadata necesitan un path de donde leerla, que debe ser relativo al punto de montaje. Supongamos que un proceso cualquiera va a querer buscar el archivo "file1" que se encuentra, por ejemplo, en /home/puntoMontaje/CarpetaEjemplo/file1.txt . Vos cuando ejecutes el proceso cliente con fuse (usando como punto de montaje el directorio puntoMontaje), esa carpeta va a tener que estar vacia. Entonces, si fuse trata de ubicar la carpeta "CarpetaEjemplo" no podria encontrarla, ya que el punto de montaje va a estar vacio. Entonces, los directorios va a haber que crearlos a mano para que el cliente con fuse detecte que "estan ahi" y pueda manejar esas peticiones? No se si se entendio

Acá hay que separar dos cosas, que no termino de entender si entendiste que son dos cosas separadas o si las tenés mezcladas pero lo que escribiste está medio ambigüo y no se nota tanto.

El punto de montaje es un directorio vacío "del filesystem padre" que usamos como """ventana""" (qué comillero estás, Mati) al FS que vamos a montar. En particular, una vez montado el FS, en el punto de montaje ya no vemos el (nulo) contenido del directorio ese, si no el contenido del FS montado.

Siendo bien bestia, pensá en "montar un FS" como "enchufarle un pendrive a una carpeta de tu disco rígido". Si esa frase la dijera un ayudante de Sistemas Operativos, es para matarlo - pero quizá es la forma más probable de que, no se, mi vieja, entienda la idea. El punto de montaje (en el ejemplo para mi vieja sería """la carpeta D:\""") tiene que estar vacío, y cuando monto el FS ("enchufo el pendrive"), en esa carpeta paso a ver el contenido del FS montado.

Si en mi pendrive tengo una carpeta llamada Operativos que adentro tiene dos archivos charmander.pdf y piki-piki.mp4, cuando monte mi pendrive en D:\, yo voy a pedirle al SO abrir el archivo D:\Operativos\charmander.pdf, y el FS que maneje mi pendrive va a recibir el pedido para abrir \Operativos\charmander.pdf.

Y esto, ¿por qué? Bueno, porque yo en Windows puedo llevar ese pendrive a otra máquina tenga la lectora de CD en D:, y entonces el pendrive se monta en E:\. En este caso, yo voy a pedir E:\Operativos\charmander.pdf, porque el punto de montaje es otro (E:\), pero el FS (que sólo administra el contenido del pendrive, y no del resto del disco) sigue teniendo que hacer lo mismo que antes: abrir el archivo charmander.pdf que está en el directorio Operativos en la raíz del pendrive.

En UNIX (Linux, Minix, MacOS X, ...) esto es más explícito aún, porque los FS no se montan en "unidades" diferentes como en Windows (A:\, C:\, D:\), si no que se montan en cualquier directorio que esté vacío. Pero el principio es el mismo: si yo monto ese pendrive en /media/utnso/PENDRIVE_DE_MATI/, voy a pedirle al SO abrir /media/utnso/PENDRIVE_DE_MATI/Operativos/charmander.pdf, y el FS sólo va a enterarse de /Operativos/charmander.pdf - porque todo lo anterior es externo a él.

Dicho toooooodo esto, vuelvo a lo que escribiste: si montás el FS en /home/puntoMontaje/, la existencia o no de /home/puntoMontaje/CarpetaEjemplo/file1.txt depende de que en tu FS exista /CarpetaEjemplo/file1.txt.

Y, ¿cómo podría pasar eso? Bueno, depende del FS. En un FS más o menos "normal" en el que se graban, leen y borran archivos y directorios (ext2/3/4, FAT32, NTFS... OSADA, incluso), un archivo va a existir si algún usuario creó ese archivo en ese directorio. Lo que estamos acostumbrados, bah: si lo que montás en /home/puntoMontaje/ es el pendrive del ejemplo anterior, la ruta esa va a existir si alguien creó una carpeta CarpetaEjemplo en la raíz (al lado de la Operativos que yo nombré), y le metió adentro un file1.txt. Lo mismo si montaras tu Pokedex Cliente en /home/puntoMontaje y el archivo de disco que está administrando el Pokedex Servidor al que se conecta ese Cliente tiene creado ese archivo file1.txt en ese directorio.

Pero vos ya viste el ejemplo de FUSE, y ya viste que lo que el FS diga es la verdad absoluta para el kernel. Si modificáramos el ejemplo de FUSE para que siempre diga que existe un directorio CarpetaEjemplo en la raíz, conteniendo un archivo file1.txt - ¡voilá! Tenemos un FS "no tan normal" para el que esa ruta es válida.

En el caso del Pokedex, OSADA y el TP, sí: para que exista esa ruta, alguien va a tener que haber creado ese directorio y ese archivo. Como nota de color, para tomar más dimensión de toooodo lo que implica tener este FS distribuído y blah, acordate que el Servidor puede tener muchos Clientes. Entonces, si montaste tu PokeCliente en /home/puntoMontaje, no es estrictamente necesario que crees ese archivo/directorio haciendo mkdir /home/puntoMontaje/CarpetaEjemplo - quizá lo crean desde otra máquina, que incluso puede haber montado el FS en otro punto de montaje. Si en otra máquina montan el PokeCliente en /media/utnso/pokedex y hacen mkdir /media/utnso/pokedex/CarpetaEjemplo, sufi - se va a crear ese directorio en el PokeServidor, y entonces va a ser visible desde todos los Clientes.

El universo es muy confuso realmente

juanoubi6 commented 8 years ago

Se entendio bastante todo, muchas gracias. Te hago otra consulta con respecto a otro tema: Cuando un programa hace, por ejemplo, una lectura sobre un archivo del punto de montaje (digamos que hace un fopen de /home/mnt/hello.txt para leerlo) y el cliente captura esa peticion de lectura y la maneja con su nueva funcion (llamemosla nuevoLeer) que es de la forma static int nuevoLeer(const char * path, char buffer, size_t size, off_t offset, struct fuse_file_info fi) ¿El path que le llega a esta funcion es /home/mnt/hello.txt o /mnt/hello.txt ? Es decir, los paths que le llegan a mis funciones son tal cual los abre el proceso que realizo el fread o el fuse los toma de alguna otra manera?

gastonprieto commented 8 years ago

Los paths son absolutos desde tu montaje, o sea si montaste tu filesystem en /home/mnt y desde afuera tratan de leer el archivo /home/mnt/hello.txt a vos te va a llegar una operación sobre /hello.txt, porque tu filesystem no tiene noción sobre donde esta montado (y no le importa)

mgarciaisaia commented 8 years ago

Gatón spoileó un poco, pero la mejor sugerencia que tengo es: probalo.

Pensá qué te parece que debería pasar (o cómo te gustaría que fuera en un mundo ideal), y por qué. Y después probalo y fijate.

Esa es una de las cosas más lindas de nuestra profesión: armar un puente para probar una teoría es un tanto costoso, pero ver si FUSE te manda un path completo o relativo o qué es bastante más sencillo de probar.

juanoubi6 commented 8 years ago

Gracias, pude probar lo de los path y efectivamente me llega como dijo Gaston. Estoy teniendo un problema al momento de leer el archivo de mi filesystem recien montado usando cat. Cuando uso cat hello para leer el ejemplo que nos dieron de fuse me aparece por consola "Hello World!". Al querer usar una funcion que no este implementada (por ejemplo, mkdir) me tira mkdir: no se puede crear el directorio «nuevoDir»: Función no implementada que es correcto ya que en el ejemplo no se implementa esa funcion.

Lo que yo hice en mi cliente fue que al hacerle una peticion de lectura a mi punto de montaje siempre devuelva "holaProbando". El punto de montaje al igual que el ejemplo contiene un solo archivo llamado hello y el contenido no es relevante. El problema que tengo es que al hacer cat hello la consola me dice lo siguiente: utnso@utnso40:~/puntoMontada$ cat hello holaProcat: hello: Error de entrada/salida cat: hello: Error de entrada/salida Es como que hizo parte de lo que yo le dije que haga (tenia que mostrar holaProbando y mostro holaPro, otra cosa que no se por que esta pasando), y me tiro esos errores de entrada y salida que no se que son. El codigo de la funcion que le asigne a la lectura es este: _char * hola = malloc(50); hola = "holaProbando"; sizet len; len = strlen(hola); memcpy(buffer, hola, size); return size;

Despues, probando hacer mkdir prueba sobre mi punto de montaje (la funcion para mkdir esta creada, pero lo unico que hace es retornar 0) me tira el siguiente error: mkdir: no se puede crear el directorio «jeje»: No existe el archivo o el directorio . Yo sabia que no iba a crear el directorio porque no hay nada implementado, pero no esperaba que me tire un error (ya que como el cliente retorna 0 le estaria diciendo al SO que todo salio bien).

Edit 1: Pude arreglar el tema de que no leia completo el contenido del archivo. Lo que yo queria leer tenia mas longitud que el mensaje que puse en #define DEFAULT_FILE_CONTENT. Agrandando esto me dejo obtener el holaProbando completo, pero sigo con los errores esos de entrada/salida

mgarciaisaia commented 8 years ago

@juanoubi6: FUSE tiene un parámetro -d para correr en modo debug, loggeando en pantalla todas las operaciones que realiza. Probá con eso, a ver si te ayuda a entender qué está pasando (por las operaciones no implementadas y blah).

Por el cachito de código que pusiste, si eso es copypasteado, veo dos temas. El primero, que no afecta tanto a este caso, es que estás reservando memoria con el malloc(50), guardás el puntero a esa memoria en hola, y después... ¡hacés que hola apunte a otra zona de memoria! Tu programa está leakeando 50 bytes, porque sí.

El segundo problema, más relevante, es que el memcpy lo estás haciendo por size bytes, que (asumo, porque no lo mostraste acá) es uno de los parámetros que le está pasando FUSE a tu función. Hay buenas chances de que ese valor no tenga tanto que ver con el contenido que estás queriendo devolver - sobretodo porque es probable que hayas cambiado este método, pero no el stat, por lo que tu FS debe estar diciendole a FUSE que tu archivo tiene un tamaño distinto al del contenido que estás devolviendo.


Changos, acabo de leer tu Edit ¯_(ツ)_/¯

guillermo126 commented 8 years ago

buenas al leer este issue pude entender varias cosas, pero me qdaron algunas dudas sobre el main del ejemplo de fuse: 1)¿que es lo que hace esta linea de codigo? struct fuse_args args = FUSE_ARGS_INIT(argc, argv); ¿eso permite que se monte el file system? porque algo de eso dice arriba y no me qda claro 2)esta linea de codigo: return fuse_main(args.argc, args.argv, &hello_oper, NULL); no estoy entendiendo que es "args.argc" y "args.argv " ¿a &hello_oper le puedo pasar cualquier funcion o le tengo que pasar todas con la q va a laburar nuestro file system? 3) en el enunciado ustedes pusieron, para cuando ejecutemos el entrenador va a ser de la siguiente manera: "./entrenador Ash /mnt/pokedex" asumo que al termina de leer todo este issue: "/mnt/pokedex" es el punto de montaje es decir q es la raiz y por lo tanto no va haber ningun archivo despues Ash es otro directorio donde voy a obtener el metada de en este caso ash, cualquier cosa si me equiboco corregime. igual la cuestion es que mas adelante cuando explican los archivos y directorios dice: /entrenadores/[nombre]/metada es decir que (siguiendo el ejemplo de ash) el archivo metada estaria en: /mnt/pokedex/entrenadores/Ash/metada ¿esta bien lo q digo o es cualquiera?

mgarciaisaia commented 8 years ago

¡Aloha!

1) La verdad, la verdad... no lo se. Hay algo que parece ser una función (aunque, por el nombre todo en mayúsculas, no sería raro que fuera una macro), que recibe argc y argv (los parámetros que recibe la función main() en C para indicar cuántos - y cuáles - argumentos recibió el programa), y devuelve una estructura que parece ser propia de FUSE (dado que es de tipo struct fuse_args.

Lo que sí se es que FUSE puede recibir algunos parámetros (que normalmente espera que el programa reciba por línea de comandos y se los pase a él), y entonces asumo que esa función debe hacer algún tratamiento que FUSE necesite/considere oportuno con los parámetros de la línea de comandos, para dejarlos listos para que FUSE los reciba.

Y, ¿cómo los recibe? Bueno, con la línea que marcás en 2): fuse_main es la función que tenemos que llamar para decirle FUSE, hacé tu magia. Lo interesante acá es que ayuda a resolver un poco la pregunta 1: efectivamente le estamos pasando dos campos (argc y argv) de la estrcutura args que acabamos de crear a fuse_main.

En este momento, un poco me pregunto cuán necesario sea hacer todo eso, ¿no? Digo, primero juntamos ambos campos en una estructura, y después le pasamos los campos de esa estructura (¡por separado!) a FUSE. Si me arriesgo a conjeturar (posta que no lo se), yo diría que esa función/macro FUSE_ARGS_INIT debe "filtrar" los parámetros de la línea de comandos, para quedarse únicamente con los que a FUSE le interesan - evitando, así, pasarle a fuse parámetros que sean propios de nuestro programa, si los hubiera.

hello_oper es la estructura mediante la cual le contamos a FUSE cuáles son nuestras implementaciones de las funciones que él soporta. Es decir, ahí vamos a decirle que la función readdir la implementamos con nuestra función hello_readdir, por ejemplo.

No es obligatorio implementar todas las operaciones, pero hay algunas que son bastante más importantes que otras. Pegale una mirada al tuto de FUSE para ver si ahí explica algo de cuáles son obligatorias o no, pero, de todos modos, cuando vayas ejecutando tu FS vas a ir notando cuáles te van a ser necesarias para poder cumplir con toda la funcionalidad que pedimos en el TP.

Más allá de todo esto, la verdad es que la respuesta posta posta la tiene la documentación de FUSE... Que no parece ser la mejor.

FUSE_ARGS_INIT apenas si inicializa la estructura fuse_args (que no aporta mucho más que guardar el argc y argv), y fuse_main efectivamente monta el FS. fuse_main_real (lo que realmente ejecuta fuse_main) recibe un fuse_operations que... buen, ahí sí tenés documentación para leer :) Una por una te explica las funciones.

Por último, respecto a 3), "ni"... "so"...

Efectivamente, para probar el TP, si corremos ./entrenador Ash /mnt/pokedex significa que ese directorio va a estar dentro del FS. Y, siendo que el directorio padre es /mnt (que es un directorio estándar de UNIX), probablemente pokedex sea la raíz del FS.

Pero, ojo - también podríamos montar el Pokedex Cliente en, no se, /mnt/undirectorio/vacio, y ejecutar ./entrenador Ash /mnt/undirectorio/vacio/pokedex/contenido - significando que en la raíz del FS existe un directorio pokedex, que contiene a uno llamado contenido, y que ahí adentro vamos a poder encontrar directorios como Entrenadores/ y demás.

Por lo demás (incluído el "cálculo" del archivo en que se encontraría la metadata de Ash), 👍👍.

¡Un abrazo!

tferraro commented 8 years ago

Holis! @guillermo126 @juanoubi6 les sirvieron las respuestas?

Saludos!

juanoubi6 commented 8 years ago

Si, lo que no me quedo muy claro (y que todavia no resolvi) fue el tema este:

Lo que yo hice en mi cliente fue que al hacerle una peticion de lectura a mi punto de montaje siempre devuelva "holaProbando". El punto de montaje al igual que el ejemplo contiene un solo archivo llamado hello y el contenido no es relevante. El problema que tengo es que al hacer cat hello la consola me dice lo siguiente: utnso@utnso40:~/puntoMontada$ cat hello holaProcat: hello: Error de entrada/salida cat: hello: Error de entrada/salida Es como que hizo parte de lo que yo le dije que haga (tenia que mostrar holaProbando y mostro holaPro, otra cosa que no se por que esta pasando), y me tiro esos errores de entrada y salida que no se que son. El codigo de la funcion que le asigne a la lectura es este:

Me sigue tirando esos errores de entrada/salida y no me lee como lee el ejemplo.

Otra consulta que me surgio fue para que sirve el getattr. Por lo que vi sirve para, a la hora de levantar un archivo, darle al que lo haya pedido los datos mas "estructurales" del archivo (tipo, dueño,permisos) pero no se si esto se hace antes de abrirse, despues, etc. Sin embargo, en nuestros procesos mapa y entrenador me imagino que nunca se usa una llamada getattr asi de forma explicita. Mirando un poco mas me fije que la libreria de las commons que levanta las configs (que usamos para levantar los datos de los archivos de la metadata del entrenador y mapa) en un momento crea una estructura del tipo struct stat que justamente es la que recibe por parametro el getattr.

t_config *config_create(char *path) {
    FILE* file = fopen(path, "r");

    if (file == NULL) {
        return NULL;
    }

    struct stat stat_file; 
stat(path, &stat_file);  //esta va a ser la funcion que "invoque" al getattr?

Ademas, como se completa la estructura stat? Eso es algo que deberia manejar el cliente por si mismo o necesita ayuda del servidor y el archivo del osdadaFS?

mgarciaisaia commented 7 years ago

Me sigue tirando esos errores de entrada/salida y no me lee como lee el ejemplo.

Acá hay que arremangarse, debuggear, mirar bien el código, ver qué está pasando... Probá de ir cambiando el string, a ver si siempre te devuelve la misma cantidad de bytes.. Fijate si tu proceso que implementa FUSE sigue funcionando, o si se aborta cuando salta ese error. Correlo con valgrind, o con el debugger de eclipse, y fijate si te están matando el proceso con alguna señal. Mirate el video de reportar errores en Eclipse para que podamos darte una mano - y abrí un issue aparte, porque es más un problema de programación que de entender FUSE, como lo es este issue. Y, si nada de eso te soluciona - venite a ver a algún ayudante en los sábados de soporte.

Otra consulta que me surgio fue para que sirve el getattr. Por lo que vi sirve para, a la hora de levantar un archivo, darle al que lo haya pedido los datos mas "estructurales" del archivo (tipo, dueño,permisos) pero no se si esto se hace antes de abrirse, despues, etc.

¡Probalo! Ahí vas a ver cuándo se llama getattr.

Sin embargo, en nuestros procesos mapa y entrenador me imagino que nunca se usa una llamada getattr asi de forma explicita.

No se si explícita, pero, de todos modos, getattr es una de las opearaciones fundamentales - quizá no la llamás directamente desde tu código, pero FUSE la llama para resolver otras operaciones.

Mirando un poco mas me fije que la libreria de las commons que levanta las configs (que usamos para levantar los datos de los archivos de la metadata del entrenador y mapa) en un momento crea una estructura del tipo struct stat que justamente es la que recibe por parametro el getattr.

Sí, stat creo que se mapea (no se cuán directa o indirectamente) a getattr. Pero no es únicamente por stat que se llama getattr, tampoco.

Ademas, como se completa la estructura stat? Eso es algo que deberia manejar el cliente por si mismo o necesita ayuda del servidor y el archivo del osdadaFS?

¿Qué campos tiene la estructura? ¿Qué información necesitás conocer para poder completarlos?

afilgueira commented 7 years ago

Buenas, habiendo pasado tanto tiempo de la ultima respuesta... solucionamos sus problemas? Podemos cerrar el issue?

afilgueira commented 7 years ago

Voy a asumir que si. Si no es el caso, por favor reabran el issue!

chrisgel15 commented 7 years ago

+1 Guía oficial de FUSE. Reemplaza a la del muchacho Pfeiffer.

Saludos!

mgarciaisaia commented 7 years ago

@chrisgel15 ¿cuál decís?

chrisgel15 commented 7 years ago

@mgarciaisaia Todo este thread! Muy útil y claro por todo lo que se discutió y tus explicaciones!!