"Después del juego es antes del juego"
Sepp Herberger

martes, 18 de mayo de 2021

Zabbix (I): template para impresoras Brother láser color

Bueno, pues por fin me he metido en el apasionante mundo de Zabbix. Con ayuda de mis compañeros he ido montando el sistema, los clientes y los templates para cada tipo de dispositivo que se puede encontrar en mi red. Es todo un universo nuevo, pero merece la pena sobradamente.

Una de las cosas que más me interesa monitorizar son las impresoras, tanto para llevar un control histórico del gasto como para recibir avisos de que queda poca tinta/tóner e ir encargando con repuestos tiempo. Para las impresoras HP y Epson que tenemos en los centros ya hay templates de monitorización que han compartido los compañeros, pero tengo por mi parte varias impresoras Brother láser color (DCP-9020CDW, HL-4140CN y alguna más) para las cuales el número de templates que se encuentran es bastante escaso. En la página de templates oficial de Zabbix aparecen apenas 3 plantillas, todas para láser monocromo, que se pueden importar (algunas con errores que hay que corregir editando a mano XML, pues son un poco antiguas), pero no nos dan toda la información que queremos sobre las impresoras láser en color.

Me ha tocado investigar un poco para crear una plantilla mezcla de las 3 existentes más items nuevos para obtener los datos que faltaban. Esta es la parte divertida de nuestro trabajo, no todo iba a ser azotar usuarios. No ha sido todo nuevo, puesto que las impresoras son interrogadas usando el protocolo SNMP y ya tenía experiencia al haberlo utilizado hace bastante tiempo para otra entrada del blog.

AVISO: tochazo inside.

1. Uso de snmpwalk para interrogar impresoras.

Para preguntar sobre toda la información que comparte de forma pública una impresora usamos el comando:
# snmpwalk -v 2c -c public 172.a.b.c
iso.3.6.1.2.1.1.1.0 = STRING: "Brother NC-8500h, Firmware Ver.1.15  (15.10.15),MID 8CE-403,FID 2"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.2435.2.3.9.1
iso.3.6.1.2.1.1.3.0 = Timeticks: (9993270) 1 day, 3:45:32.70
iso.3.6.1.2.1.1.4.0 = STRING: "contacto@gmail.es"
iso.3.6.1.2.1.1.5.0 = STRING: "BN_SALAPROFESORES"
iso.3.6.1.2.1.1.6.0 = STRING: "Sala de Profesores"
iso.3.6.1.2.1.1.7.0 = INTEGER: 72
iso.3.6.1.2.1.1.8.0 = Timeticks: (0) 0:00:00.00
iso.3.6.1.2.1.1.9.1.2.1 = OID: iso.3.6.1.6.3.1
iso.3.6.1.2.1.1.9.1.2.2 = OID: iso.3.6.1.6.3.10.3.1.1
iso.3.6.1.2.1.1.9.1.2.3 = OID: iso.3.6.1.6.3.11.3.1.1
iso.3.6.1.2.1.1.9.1.2.4 = OID: iso.3.6.1.6.3.15.2.1.1
iso.3.6.1.2.1.1.9.1.2.5 = OID: iso.3.6.1.6.3.16.2.1.1
iso.3.6.1.2.1.1.9.1.3.1 = STRING: "The MIB Module from SNMPv2 entities"
iso.3.6.1.2.1.1.9.1.3.2 = STRING: "SNMP Management Architecture MIB"
iso.3.6.1.2.1.1.9.1.3.3 = STRING: "Message Processing and Dispatching MIB"
iso.3.6.1.2.1.1.9.1.3.4 = STRING: "USM User MIB"
iso.3.6.1.2.1.1.9.1.3.5 = STRING: "VACM MIB"
.......................
.......................
.......................
La lista es bastante grande y normalmente ahí hay información de sobra sobre el estado de nuestra impresora. Cada rama o nodo tiene un significado más o menos estándar (aunque recordemos el axioma que dice "lo bueno de los estándares es que hay varios donde elegir"). Por ejemplo, de la lista anterior el OID iso.3.6.1.2.1.1.6 se describe como:

Literalmente: "The physical location of this node (e.g., 'telephone closet, 3rd floor'). If the location is unknown, the value is the zero-length string", es decir, la ubicación física de la impresora tal como está definida en su configuración. El valor de este campo se obtiene con:
# snmpwalk -v 2c -c public 172.a.b.c iso.3.6.1.2.1.1.6
iso.3.6.1.2.1.1.6.0 = STRING: "Sala de Profesores"
La información sobre el contenido cada OID (object identifier o Identificador de Objeto, que se define como una secuencia de números que se asignan jerárquicamente y que permite identificar objetos en la red, siendo usados con gran cantidad de protocolos) estándar se puede consultar en varios sitios de internet:
2. Problemática con las impresoras Brother.

Hasta aqui todo sería estupendo si todas las impresoras respetasen los OID estándar. Con las HP y las Epson no hay problema, pero mis Brother son rebeldes. La mayoría de los OID (ubicación, status, páginas impresas, número de serie, etc) se ajustan al estándar definido y vale el mismo template que para cualquier otra impresora, pero aquellos que deben decirme el nivel de tóner restante mienten o son inexactos.

El estado de los consumibles se encuentra en la rama iso.3.6.1.2.1.43.11, prtMarkerSupplies. Vamos a husmear dentro (añado comentarios explicando el significado de las ramas):
# snmpwalk -v 2c -c public 172.19.231.51 iso.3.6.1.2.1.43.11                       
iso.3.6.1.2.1.43.11.1.1.2.1.1 = INTEGER: 1   --- prtMarkerSuppliesMarkerIndex(2)
iso.3.6.1.2.1.43.11.1.1.2.1.2 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.3 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.4 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.5 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.6 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.7 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.8 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.9 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.2.1.10 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.3.1.1 = INTEGER: 1   --- prtMarkerSuppliesColorantIndex(3)
iso.3.6.1.2.1.43.11.1.1.3.1.2 = INTEGER: 2
iso.3.6.1.2.1.43.11.1.1.3.1.3 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.3.1.4 = INTEGER: 4
iso.3.6.1.2.1.43.11.1.1.3.1.5 = INTEGER: 0
iso.3.6.1.2.1.43.11.1.1.3.1.6 = INTEGER: 0
iso.3.6.1.2.1.43.11.1.1.3.1.7 = INTEGER: 0
iso.3.6.1.2.1.43.11.1.1.3.1.8 = INTEGER: 0
iso.3.6.1.2.1.43.11.1.1.3.1.9 = INTEGER: 0
iso.3.6.1.2.1.43.11.1.1.3.1.10 = INTEGER: 0
iso.3.6.1.2.1.43.11.1.1.4.1.1 = INTEGER: 3   --- prtMarkerSuppliesClass(4)
iso.3.6.1.2.1.43.11.1.1.4.1.2 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.3 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.4 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.5 = INTEGER: 4
iso.3.6.1.2.1.43.11.1.1.4.1.6 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.7 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.8 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.9 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.4.1.10 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.5.1.1 = INTEGER: 3  --- prtMarkerSuppliesType(5) : tipo de consumible
iso.3.6.1.2.1.43.11.1.1.5.1.2 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.5.1.3 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.5.1.4 = INTEGER: 3
iso.3.6.1.2.1.43.11.1.1.5.1.5 = INTEGER: 4
iso.3.6.1.2.1.43.11.1.1.5.1.6 = INTEGER: 1
iso.3.6.1.2.1.43.11.1.1.5.1.7 = INTEGER: 9
iso.3.6.1.2.1.43.11.1.1.5.1.8 = INTEGER: 9
iso.3.6.1.2.1.43.11.1.1.5.1.9 = INTEGER: 9
iso.3.6.1.2.1.43.11.1.1.5.1.10 = INTEGER: 9
iso.3.6.1.2.1.43.11.1.1.6.1.1 = STRING: "Black Toner Cartridge"  --- prtMarkerSuppliesDescription(6) : descripción del consumible. 
iso.3.6.1.2.1.43.11.1.1.6.1.2 = STRING: "Cyan Toner Cartridge"
iso.3.6.1.2.1.43.11.1.1.6.1.3 = STRING: "Magenta Toner Cartridge"
iso.3.6.1.2.1.43.11.1.1.6.1.4 = STRING: "Yellow Toner Cartridge"
iso.3.6.1.2.1.43.11.1.1.6.1.5 = STRING: "Waste Toner Box"
iso.3.6.1.2.1.43.11.1.1.6.1.6 = STRING: "Belt Unit"
iso.3.6.1.2.1.43.11.1.1.6.1.7 = STRING: "Black Drum Unit"
iso.3.6.1.2.1.43.11.1.1.6.1.8 = STRING: "Cyan Drum Unit"
iso.3.6.1.2.1.43.11.1.1.6.1.9 = STRING: "Magenta Drum Unit"
iso.3.6.1.2.1.43.11.1.1.6.1.10 = STRING: "Yellow Drum Unit"
iso.3.6.1.2.1.43.11.1.1.7.1.1 = INTEGER: 13 --- prtMarkerSuppliesSupplyUnit(7) : unidad de medida del consumible.
iso.3.6.1.2.1.43.11.1.1.7.1.2 = INTEGER: 13
iso.3.6.1.2.1.43.11.1.1.7.1.3 = INTEGER: 13
iso.3.6.1.2.1.43.11.1.1.7.1.4 = INTEGER: 13
iso.3.6.1.2.1.43.11.1.1.7.1.5 = INTEGER: 13
iso.3.6.1.2.1.43.11.1.1.7.1.6 = INTEGER: 7
iso.3.6.1.2.1.43.11.1.1.7.1.7 = INTEGER: 7
iso.3.6.1.2.1.43.11.1.1.7.1.8 = INTEGER: 7
iso.3.6.1.2.1.43.11.1.1.7.1.9 = INTEGER: 7
iso.3.6.1.2.1.43.11.1.1.7.1.10 = INTEGER: 7
iso.3.6.1.2.1.43.11.1.1.8.1.1 = INTEGER: -2  --- prtMarkerSuppliesMaxCapacity(8) : máxima capacidad del consumible.
iso.3.6.1.2.1.43.11.1.1.8.1.2 = INTEGER: -2
iso.3.6.1.2.1.43.11.1.1.8.1.3 = INTEGER: -2
iso.3.6.1.2.1.43.11.1.1.8.1.4 = INTEGER: -2
iso.3.6.1.2.1.43.11.1.1.8.1.5 = INTEGER: -2
iso.3.6.1.2.1.43.11.1.1.8.1.6 = INTEGER: 50000
iso.3.6.1.2.1.43.11.1.1.8.1.7 = INTEGER: 15000
iso.3.6.1.2.1.43.11.1.1.8.1.8 = INTEGER: 15000
iso.3.6.1.2.1.43.11.1.1.8.1.9 = INTEGER: 15000
iso.3.6.1.2.1.43.11.1.1.8.1.10 = INTEGER: 15000
iso.3.6.1.2.1.43.11.1.1.9.1.1 = INTEGER: -3  --- prtMarkerSuppliesLevel(9) : nivel actual del consumible.
iso.3.6.1.2.1.43.11.1.1.9.1.2 = INTEGER: -3
iso.3.6.1.2.1.43.11.1.1.9.1.3 = INTEGER: -3
iso.3.6.1.2.1.43.11.1.1.9.1.4 = INTEGER: -3
iso.3.6.1.2.1.43.11.1.1.9.1.5 = INTEGER: -3
iso.3.6.1.2.1.43.11.1.1.9.1.6 = INTEGER: 48217
iso.3.6.1.2.1.43.11.1.1.9.1.7 = INTEGER: 4556
iso.3.6.1.2.1.43.11.1.1.9.1.8 = INTEGER: 4556
iso.3.6.1.2.1.43.11.1.1.9.1.9 = INTEGER: 4556
iso.3.6.1.2.1.43.11.1.1.9.1.10 = INTEGER: 4556
Detallo las ramas que más nos interesan:
  • prtMarkerSuppliesType, nodo iso.3.6.1.2.1.43.11.1.1.5: tipos de consumible. Por ejemplo, el valor 3 equivale a "tóner".
  • prtMarkerSuppliesDescription, nodo iso.3.6.1.2.1.43.11.1.1.6: descripción del consumible. Por ejemplo "Magenta Toner Cartridge".
  • prtMarkerSuppliesSupplyUnit, nodo iso.3.6.1.2.1.43.11.1.1.7: unidades de medida del consumible. Por ejemplo, el valor 13 equivale a "decígramos".
  • prtMarkerSuppliesMaxCapacity, nodo iso.3.6.1.2.1.43.11.1.1.8: valor máximo de capacidad del consumible: puede ser la cantidad de tóner o tinta que cabe en el cartucho, o el número máximo de páginas que tiene de vida útil un tambor/cinta de arrastre.
  • prtMarkerSuppliesLevel, nodo iso.3.6.1.2.1.43.11.1.1.9: nivel actual del consumible: puede ser la cantidad de tóner o tinta que resta en el cartucho (un %, un número estimado de páginas, etc), o el número de páginas que le quedan por imprimir a un tambor/cinta de arrastre.

Con estos indicadores se puede controlar cuanto consumible queda... en teoría. En el caso de las impresoras Brother el contenido de las ramas prtMarkerSuppliesMaxCapacity y prtMarkerSuppliesLevel no dan datos reales sobre los niveles de tóner, aunque si del estado de otros consumibles: almacén de tóner, cinta de arrastre y tambores. En ellas prtMarkerSuppliesMaxCapacity siempre tiene el valor -2 y prtMarkerSuppliesLevel lo he visto con únicamente dos valores distintos: -3 (hay toner) y 0 (no hay tóner), lo cual es muy poco informativo. En Internet se habla de otros posibles valores asociados a varios estados ("full", "almost empty", "empty" or "absent) pero en mis pruebas esos valores nunca han aparecido.

Cusiosamente, casi todos los otros parámetros estándar de impresoras si que están implementados para las Brother. Por ejemplo, hrPrinterStatus con los valores de estado de la impresora: other(1), unknown(2), idle(3), printing(4), warmup(5).

Buscando una solución encontré un parámetro llamado prtAlertcode en la rama iso.3.6.1.2.1.43.18.1.1.7 que nos devuelve un código de alerta si pasa algo en la impresora. Como se ve en el enlace anterior hay un buen número de códigos, incluido el 1104 que significa markerTonerAlmostEmpty: queda poco tóner. No nos dice el color, pero puede servir para alertar si no nos queda más remedio.

Evidentemente, con esta información es imposible poder monitorizar con detalle el nivel de los consumibles y planificar su adquisión. Pero esto no puede quedar aquí: si vamos al interface web o el panel de la impresora Brother vemos que si que conocen y muestran esos niveles. Es obvio que tiene que haber alguna manera de interrogar a la impresora para obtenerlos.

3. Buscando e interpretando los OID privados.

Mirando con mas detalle me encontré que hay ciertas ramas "ocultas" o privadas que dependen del fabricante del dispositivo y que no salen haciendo un snmpwalk normal. El nodo sysObjectID, con OID iso.3.6.1.2.1.1.2 contiene "The vendor's authoritative identification of the network management subsystem contained in the entity". En caso de las Brother su contenido es:
# snmpwalk -v 2c -c public 172.a.b.c 1.3.6.1.2.1.1.2
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.2435.2.3.9.1
Es decir, ese nodo contiene una referencia a otra rama, que en el caso de la Brother es iso.3.6.1.4.1.2435. Esta es una rama oculta, que si consultamos directamente con:
# snmpwalk -v 2c -c public 172.a.b.c 1.3.6.1.4.1.2435
.....
.....
.....
Muestra mas de 5000 nodos con mucha información privada de la impresora Brother. El código 2435 corresponde a Brother, pero como se puede ver en este enlace hay códigos para todo tipo de fabricantes, asignados por IANA. Cada uno tendrá su rama privada colgando de ahí.

En https://oidref.com/ podemos perdernos husmeando dentro de esa rama. Yo he ido al grano, buscando valores que me interesaban para su monitorización con Zabbix, por ejemplo el nodo brBackLightColor, que tiene 4 posibles valores: 0:Off/1:Green/2:Orange/3:Red. Básicamente sirven para indicarnos el color que muestra led del panel de la impresora, 0 o 1 significa que bien, y 2 o 3 que hay problemas. Los templates de Zabbix que encontré para impresoras Brother monocromo usaban brBackLightColor en conjunción con prtAlertCode (visto anteriormente) para avisar de forma rudimentaria de que quedaba poca tinta.

Otra rama que me ha venido bien es brPrintPages, iso.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.52.1.3, que muestra desglosadas las páginas impresas en B/N y en color.

Pero no olvidemos que la meta es saber los niveles de los 4 tóneres de color. No hallaba información alguna hasta que encontré pistas en este enlace, y sobre todo en este otro, donde se muestra un programa que lee los niveles reales de tóner de una Brother color. Los datos se leen por SNMP mediante este conjunto de scripts en Python. Indagando en el código veo que los niveles de tinta se sacan de la rama iso.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0, llamada brInfoMaintenance. ¿Qué hay allí?:
# snmpwalk -v 2c -c public 172.a.b.c iso.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8
iso.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0 = Hex-STRING: 76 01 04 00 00 00 01 77 01 04 00 00 00 01 78 01 
04 00 00 00 01 7F 01 04 00 00 00 01 68 01 04 00 
00 00 01 55 01 04 00 00 00 01 31 01 04 00 00 00 
01 32 01 04 00 00 00 02 33 01 04 00 00 00 01 34 
01 04 00 00 00 01 70 01 04 00 00 01 F4 82 01 04 
00 00 00 0A 71 01 04 00 00 22 60 83 01 04 00 00 
00 5A 72 01 04 00 00 19 64 84 01 04 00 00 00 46 
6F 01 04 00 00 25 1C 81 01 04 00 00 00 64 79 01 
04 00 00 0C 1C 7A 01 04 00 00 0C 1C 7B 01 04 00 
00 0C 1C 80 01 04 00 00 0C 1C 69 01 04 00 00 25 
E4 73 01 04 00 00 28 FA 74 01 04 00 00 28 FA 75 
01 04 00 00 28 FA 7E 01 04 00 00 28 FA 54 01 04 
00 00 00 01 35 01 04 00 00 00 01 6A 01 04 00 00 
21 34 6D 01 04 00 00 26 AC FF 
Mmmm, esto es una retahíla hexadecimal incomprensible...

4. Extrayendo información de brInfoMaintenance

Bueno, pues ahi dentro está la información. Y alguien ha hecho ingeniería inversa para extraerla. Tras leer dicha página comprendo que, para las impresoras que tengo, la información se organiza en septetos de bytes, asi que formateo la salida del comando para que salga en grupos de 7 valores::
# snmpwalk -v 2c -c public 172.a.b.c iso.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0 | tr -d "\n" | cut -c54- |  fold -w 21
76 01 04 00 00 00 01 
77 01 04 00 00 00 01 
78 01 04 00 00 00 01 
7F 01 04 00 00 00 01 
68 01 04 00 00 00 01 
55 01 04 00 00 00 01 
31 01 04 00 00 00 01 
32 01 04 00 00 00 02 
33 01 04 00 00 00 01 
34 01 04 00 00 00 01 
70 01 04 00 00 01 F4 
82 01 04 00 00 00 0A 
71 01 04 00 00 22 60 
83 01 04 00 00 00 5A 
72 01 04 00 00 19 64 
84 01 04 00 00 00 46 
6F 01 04 00 00 25 1C 
81 01 04 00 00 00 64 
79 01 04 00 00 0C 1C 
7A 01 04 00 00 0C 1C 
7B 01 04 00 00 0C 1C 
80 01 04 00 00 0C 1C 
69 01 04 00 00 25 E4 
73 01 04 00 00 28 FA 
74 01 04 00 00 28 FA 
75 01 04 00 00 28 FA 
7E 01 04 00 00 28 FA 
54 01 04 00 00 00 01 
35 01 04 00 00 00 01 
6A 01 04 00 00 21 34 
6D 01 04 00 00 26 AC 
FF 
Así formateado, verifico que el significado del primer byte se describe en esta tabla:
"11": VAL_DRUM_COUNT
"31": VAL_BLACK_TONER_STATUS
"32": VAL_CYAN_TONER_STATUS
"33": VAL_MAGENTA_TONER_STATUS
"34": VAL_YELLOW_TONER_STATUS
"41": VAL_DRUM_REMAIN
"63": VAL_DRUM_STATUS
"69": VAL_BELT_REMAIN
"6a": VAL_FUSER_REMAIN
"6b": VAL_LASER_REMAIN
"6c": VAL_PF_MP_REMAIN
"6d": VAL_PF_1_REMAIN
"6f": VAL_BLACK_TONER_REMAIN
"70": VAL_CYAN_TONER_REMAIN
"71": VAL_MAGENTA_TONER_REMAIN
"72": VAL_YELLOW_TONER_REMAIN
"73": VAL_CYAN_DRUM_COUNT
"74": VAL_MAGENTA_DRUM_COUNT
"75": VAL_YELLOW_DRUM_COUNT
"7e": VAL_BLACK_DRUM_COUNT
"79": VAL_CYAN_DRUM_REMAIN
"7a": VAL_MAGENTA_DRUM_REMAIN
"7b": VAL_YELLOW_DRUM_REMAIN
"80": VAL_BLACK_DRUM_REMAIN
"81": VAL_BLACK_TONER
"82": VAL_CYAN_TONER
"83": VAL_MAGENTA_TONER
"84": VAL_YELLOW_TONER
"a1": VAL_BLACK_TONER_REMAIN
"a2": VAL_CYAN_TONER_REMAIN
"a3": VAL_MAGENTA_TONER_REMAIN
"a4": VAL_YELLOW_TONER_REMAIN
A continuación vienen los valores fijos "01 04" y los cuatro últimos bytes son el valor del parámetro, por ejemplo:
71 01 04 00 00 23 F0
El "71" es VAL_MAGENTA_TONER_REMAIN, tóner restante magenta. El "01 04" lo ignoramos. Y el "00 00 23 F0" es el número hexadecimal 0x23F0, que en decimal es 9200. Ese 9200 se debe dividir por 100 y nos da el porcentaje de toner magenta restante: 9200/100=92% de tóner. ¡Ahí está la información!. Por tanto, en:
"6f": VAL_BLACK_TONER_REMAIN
"70": VAL_CYAN_TONER_REMAIN
"71": VAL_MAGENTA_TONER_REMAIN
"72": VAL_YELLOW_TONER_REMAIN
Podremos sacar el % de cada cartucho de tóner. Puñeteramente endiablado lo ha puesto Brother. Menos mal que un esforzado hacker de los buenos lo ha decodificado, Gloria y Honor para él.

5. El template Zabbix final.

Bueno, pues el template debe extraer el churro hexadecimal de iso.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0, buscar los códigos 6F, 70, 71 y 72 para cada cartucho de tóner y extraer el nivel en cada uno.

La manera mas sencilla que he encontrado es crear un item que lee la secuencia hexadecimal completa por SNMP y luego hace un preprocessing con Javascript (me encanta ese lenguaje) que busca el código identificador del cartucho en cuestión, extrae los dos bytes con el valor, los convierte de hexadecimal a decimal y lo divide por 100 para obtener el valor que será el mostrado por el item. El código para el tóner Cyan sería:
var pos = value.indexOf("70 01 04 00 00");
var res=0;
if (pos >=0 ) { 
   res=value.substring(pos+15, pos+20);
   res=parseInt(res.replace(" ", ""),16)/100;
};
return res;
El ítem completo sería:
<item>
      <name>Brother Info Maintenance Cyan</name>
      <type>SNMP_AGENT</type>
      <snmp_oid>1.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0</snmp_oid>
      <key>brInfoMaintenanceCyan</key>
      <delay>15m</delay>
      <units>%</units>
      <applications>
        <application>
          <name>Supplies</name>
        </application>
      </applications>
      <preprocessing>
        <step>
          <type>JAVASCRIPT</type>
          <params>
            var pos = value.indexOf("70 01 04 00 00");
            var res=0;
            if (pos >=0 ) { 
                res=value.substring(pos+15, pos+20);
                res=parseInt(res.replace(" ", ""),16)/100;
            };
            return res;
          </params>
        </step>
      </preprocessing>
      <triggers>
        <trigger>
          <expression>{last()}<=10</expression>
          <name>Cyan Ink Low  at {HOST.NAME}</name>
          <priority>AVERAGE</priority>
          <manual_close>YES</manual_close>
        </trigger>
      </triggers>
</item>
Como se puede ver añadimos un trigger que avisa cuando el tóner está por debajo del 10%. El resto de colores se haría con el mismo método.

Finalmente, incluyo enlace Template_Brother_Laser_Printers.xml con el template Zabbix final para impresoras Brother Láser Color, en formato XML listo para ser importado y leer bien todos los niveles de tóner, junto con varios valores más adicionales que me ha parecido interesante mantener o añadir. Algo me dice que este será el template Zabbix mas enrevasado que nunca escribiré.



Estamos que no paramos con Marte. Los camaradas de la República Popular de China ha aterrizado con la discreción que le caracteriza su rover Zhurong. Parece ser que para la civilización China a la primera va la vencida. Todavía no hay imágenes directas, pero esto nos sirve como adelanto:


Esperamos ansiosos las imágenes del rover trabajando.

jueves, 13 de mayo de 2021

Envio de mensajes mediante ventana emergente al escritorio del usuario

Este asunto acerca de mostrar un mensaje en el escritorio desde una sesión de consola o ssh ya lo vimos en otra ocasión, pero he tenido que darle una vuelta más en los últimos tiempos por una petición de un profesor. Vamos a revisitarlo usando este script más afinado:
# cat /usr/bin/mensaje-escritorio.sh 
#!/bin/bash

if [ $# -ne 1 ] 
then
    echo "Uso de parámetros incorrectos"
    echo "           $0 'Internet será cortado a las 12.30 durante  media hora\'"
    echo "           Formato de mensaje en Pango Markup Language"
    exit 1
fi

fecha=$(date "+%H:%M. %d-%m-%Y")
mensaje="\n\n\nAtención:  \n\n$1."
DISPLAY=:0 XAUTHORITY=/var/run/lightdm/root/\:0 zenity --display=:0 --info --text="$mensaje" --title="Aviso ${fecha}" --width="800" --height="600" --ok-label="OK"
exit 0
Básicamente, desde consola o desde una conexión ssh mostramos en el escritorio gráfico un mensaje, que se pasa como parámetro y que ocupará la mayor parte de la pantalla. Si no hay sesión abierta el mensaje se mostrara en la pantalla de inicio de sesión. Por ejemplo:
 # mensaje-escritorio.sh "Por favor, cierre la sesión ya que se va a reiniciar el servidor dentro de los próximos <span foreground='red'><b>5 minutos</b></span>"
Se mostrará:

Como podemos observar, el mensaje puede tener formato (negrita, tamaños, colores,...) usando el Pango Markup Language, un lenguaje de marcado sencillo que permite dar formato a textos al mostrarlos en pantalla.

Ahora bien, ¿que pasa si queremos enviar el mensaje a varias máquinas a la vez?. Pues sería con este script:
# cat /usr/bin/multi-mensajes.sh 
#!/bin/bash
#Parametros
# $1=mensaje
# $2=lista maquinas

if [ $# -ne 2 ] 
then
    echo "Número incorrecto de parámetros"
    echo "           $0 \"Atencion, cierren sesión\" \"spro-o01 a02-pro a04-pro\""
    echo "           Formato de mensaje en Pango Markup Language"
    exit 1
fi
clave="password1234" 
for maquina in $2
do
  sshpass -p${clave} ssh -o StrictHostKeyChecking=no root@$maquina "/usr/bin/mensaje-escritorio.sh \"$1\" > /dev/null 2>&1 " &
done

exit 0
Por ejemplo, para mostrar un mensaje en el escritorio de 4 máquinas:
 # multi-mensajes.sh "Por favor, cierre la sesión ya que se va a reiniciar el servidor dentro de los próximos <span foreground='red'><b>5 minutos</b></span>" "spro-o01 spro-o02 a02-pro a05-pro"
Si lo hacemos muy a menudo podría ser interesante usar el comando dsh en lugar de hacer llamadas ssh individuales. Incluso podemos valorar establecer relaciones de confianza SSH para evitar tener que indicar la clave de root en cada llamada.

Bien, pues con esto tenemos una herramienta para mandar mensajes emergentes remotos desde una máquina a cualquier otra máquina del centro.

miércoles, 12 de mayo de 2021

Hacer una foto con la webcam, guardarla y enviarla por correo.

Ya hemos hecho entradas que tratan sobre monitorizar la actividad del usuario que está trabajando en un ordenador: En esta ocasión el problema era algo distinto: por razones que no vienen a cuento se necesitaba capturar una foto con la webcam y enviarla por correo a una dirección predeterminada.

Para hacerlo usaremos 2 herramientas:
  • fswebcam: es una utilidad para sacar fotos con la cámara.
  • mailsend-go: versión actualizada de una utilidad para enviar correos (y ficheros adjuntos) usando un servidor SMTP externo, por ejemplo una cuenta de gmail. Como no está empaquetada en Ubuntu hay que descargar el ejecutable y copiarlo a mano en el equipo.
El script que usaremos será:
# cat /usr/local/bin/click-and-mail

#!/bin/bash

#Añadir el parametro /m si queremos enviar un correo con el resultado de la captura

REMITENTE="cuentaenvio@gmail.com"
DESTINATARIO="cuentadestino@gmail.com"
PASSWORD="password1234"
EQUIPO=$HOSTNAME
MAIL=$1  # "/m" enviar correo, vacio: no enviarlo
FECHA=$(date '+%Y-%m-%d-%H:%M')
LOG="/root/.picture"

mkdir -p $LOG

if [ -e /dev/video0 ]
then 
    /usr/bin/fswebcam -d /dev/video0  --scale "640x480" "/tmp/snapshot.jpg"
    test "$MAIL" == "/m"  && /usr/local/bin/mailsend-go -sub "Foto $HOSTNAME"  -smtp smtp.gmail.com -port 587     \
             auth -user $REMITENTE -pass "$PASSWORD" -from "$REMITENTE" -to  "$DESTINATARIO"  \
             body      -msg "Foto"  attach      -file "/tmp/snapshot.jpg"
    mv -f "/tmp/snapshot.jpg" "${LOG}/s${FECHA}.jpg"
    echo "Captura $FECHA" >>  ${LOG}/capture.log
else
    test "$MAIL" == "/m" && /usr/local/bin/mailsend-go -sub "Foto $HOSTNAME"  -smtp smtp.gmail.com -port 587     \
             auth -user $REMITENTE -pass "$PASSWORD" -from "$REMITENTE" -to  "$DESTINATARIO"  \
             body      -msg "Foto no posible, no camara"
    echo "Fallo captura $FECHA" >>  ${LOG}/capture.log
fi

exit 0
Comentemos:
  • El parámetro "/m" hace que la foto se envíe por correo. Si no se pone solamente se guardará en local.
  • Usamos para el envío una cuenta de gmail, tipo cuentaenvio@gmail.com. Para que funcione el envío de correos desde mailsend-go hay que habilitar en esa cuenta de Google el uso de aplicaciones poco seguras.
  • El correo se envia a otra cuenta de Gmail o de cualquier otro correo, para eso no hay problema.
  • En la carpeta local /root/.picture se guardan todas las imágenes tomadas. En /root/.picture/capture.log un log del proceso.
  • Si la cámara está desactivada o no se encuentra, se comunica al usuario por correo.
¿Cómo y cúando ejecutamos el script?: eso ya depende de lo que queramos hacer. Podemos entrar por ssh en la máquina y ejecutarlo, lanzarlo desde el crontab cada cierto tiempo, lanzarlo al iniciar sesión o cuando haya algún evento que monitoricemos..


Esa máquina llamada OSIRIS-REx ha dado un piquito al asteroide Bennu y hace unas horas a emprendido su vuelta a la Tierra con unos gramos de polvo, piedras y otra materia de ese cuerpo celeste. Creo que así empezaba La Amenaza de Andrómeda. No tiren sus mascarillas aunque estén vacunados.

martes, 11 de mayo de 2021

Poner webcam a 50hz para evitar parpadeos en las videoconferencias.

Cuando estamos en una videoconferencia a veces se nota un molesto parpadeo en las imágenes que toma nuestra cámara. La causa es que en España la red eléctrica funciona a 50hz (50 ciclos por segundo), pero si la cámara está configurada para trabajar a 60hz los focos de luz (bombillas, pantallas, etc) provocan ese parpadeo, conocido en inglés como flickering. El motivo es que no hay sincronización entre los fotogramas por segundo que adquiere la cámara con la frecuencia de señal eléctrica que provoca el el foco de luz.

Para evitarlo mi compañero Toní me descubrió que hay que configurar la webcam para funcionar a 50hz, ya que muchas veces viene por defecto a 60hz (que es la frecuencia en USA y en otros países). Hay varias maneras de hacerlo, usando las utilidades v4l2ucp y v4l2-ctl (paquete v4l2-utils).

La mas inmediata es hacerlo a mano, con el programa v4l2ucp:


O bien desde línea de comandos, con:
# v4l2-ctl --set-ctrl=power_line_frequency=1
El parámetro power_line_frequency admite tres valores:
  • 0 para Desactivado.
  • 1 para 50 Hz (casi todo el mundo)
  • 2 para 60 Hz (USA y algún otro país)

La otra manera, más cómoda, es configurar el sistema para que ejecute el comando anterior automáticamente cada vez que se detecte una webcam. Para ello usamos una regla de rules.d:
# cat /etc/udev/rules.d/81-uvcvideo.rules
# Set power line frequency to European
ACTION=="add", SUBSYSTEM=="video4linux", DRIVERS=="uvcvideo", RUN+="/usr/bin/v4l2-ctl --set-ctrl=power_line_frequency=1"
De esta manera nuestro sistema estará siempre configurado para poner las cámaras a 50hz, listas para la videoconferencia.



La Starship SN15 ha aterrizado exitosamente. Estamos mas cerca de Marte:



El único problema es que, como no ha explotado, apenas le han dedicado tiempo en los Informativos. Esto es como la pandemia de COVID: solo se habla de lo que se puede criticar. Si no hablan de algo es que va bien. Me vale tanto para aterrizajes de la Starship como para ritmos de vacunación.



lunes, 10 de mayo de 2021

Misterioso cuelgue en os-prober al actualizar el grub con update-grub.

Hace poco me ha pasado algo curioso: tras hacer unos cambios en /etc/default/grub e instalar un nuevo kernel, cuando hacía:
# update-grub
Me sucedía lo siguiente:
Generando un fichero de configuración de grub...
Encontrado tema: /usr/share/grub/themes/manjaro/theme.txt
Encontrada imagen de linux: /boot/vmlinuz-5.10-x86_64
Encontrada imagen de memoria inicial: /boot/intel-ucode.img /boot/initramfs-5.10-x86_64.img
Found initrd fallback image: /boot/initramfs-5.10-x86_64-fallback.img
Encontrada imagen de linux: /boot/vmlinuz-5.9-x86_64
Encontrada imagen de memoria inicial: /boot/intel-ucode.img /boot/initramfs-5.9-x86_64.img
Found initrd fallback image: /boot/initramfs-5.9-x86_64-fallback.img
Aviso: os-prober will be executed to detect other bootable partitions.
It's output will be used to detect bootable binaries on them and create new boot entries.
Y ahi se quedaba, parado en ese punto sin acabar nunca. Probando a lanzar el comando os-prober a mano sucedía lo mismo. En cambio, si edito el fichero /etc/default/grub y pongo:
GRUB_DISABLE_OS_PROBER=true
El update-grub funciona, ya que nos estamos saltando la llamada a os-prober. Todo esto es un poco raro y como no encontraba explicación y no salía ningún mensaje por pantalla, me ha dado por mirar en el syslog. Allí me ha llamado la atención esto:
abr 28 19:48:26.014019 ubuntu-pc kernel: blk_update_request: I/O error, dev mmcblk0, sector 15205696 op 0x0:(READ) flags 0x80700 phys_seg 14 prio class 0
Vaya, errores de acceso en mmcblk0. Eso es una tarjeta microSD que tengo puesta en el ordenador. La expulso y hago update-grub otra vez: ahora funciona correctamente y me actualiza el grub sin mayor problema.
# update-grub
Generando un fichero de configuración de grub...
Encontrado tema: /usr/share/grub/themes/manjaro/theme.txt
Encontrada imagen de linux: /boot/vmlinuz-5.10-x86_64
Encontrada imagen de memoria inicial: /boot/intel-ucode.img /boot/initramfs-5.10-x86_64.img
Found initrd fallback image: /boot/initramfs-5.10-x86_64-fallback.img
Encontrada imagen de linux: /boot/vmlinuz-5.9-x86_64
Encontrada imagen de memoria inicial: /boot/intel-ucode.img /boot/initramfs-5.9-x86_64.img
Found initrd fallback image: /boot/initramfs-5.9-x86_64-fallback.img
Aviso: os-prober will be executed to detect other bootable partitions.
It's output will be used to detect bootable binaries on them and create new boot entries.
Encontrado Windows Boot Manager en /dev/sda2@/EFI/Microsoft/Boot/bootmgfw.efi
Adding boot menu entry for UEFI Firmware Settings ...
Found memtest86+ image: /boot/memtest86+/memtest.bin
hecho
Ya tranquilamente he insertado la tarjeta y hecho pruebas, verificando que si que se producen fallos al acceder a ella. La he formateado y comprobado que ya funciona bien. Nunca se me hubiera dicho que una tarjeta de memoria dañada iba a parar el update-grub (o cualquier actualización del sistema que implique un update-grub) sin dar ningún mensaje de aviso o error en consola.




Un helicóptero volando en la atmósfera de otro planeta, alucina,vecina. Se ha tardado en llegar hasta aquí, pero nos esperan unos años apasionantes. Como contrapartida no puedo evitar poner este hatajo de ignorantes haciendo alarde de su necedad:


Vergüenza ajena es lo que dan.

jueves, 4 de marzo de 2021

Iniciar sesión hacia una conexión de escritorio remoto sin cargar el entorno de escritorio local.

El objetivo de esta entrada es lograr que al iniciar la sesión en Linux nuestra máquina no cargue el escritorio local, sino que se conecte a una máquina remota y muestre su escritorio de forma automatica, de tal manera que el usuario inicie una sesión de escritorio remoto de forma transparente para él. La conexión puede ser mediante rdesktop/freerdp si la máquina remota es un Windows, o x2go/vnc si la máquina remota es Linux. Es lo que se llama "modo kiosko", en el que nuestro ordenador ejecuta una única aplicación que ocupa toda la pantalla.

La finalidad de este montaje es usar un PC muy antiguo y limitado en recursos como "terminal tonto" que conecte y abra sesión en otro que está sobrado de ellos. Esta sesión se abrirá ejecutando en pantalla completa la aplicación cliente de conexión, sin cargar ningún escritorio local, de manera que se minimicen los recursos usados en la máquina cliente. De esta manera no llegaremos al punto de optimización de los thinclients en un sistema LTSP como tenemos en las aulas, pero al menos podremos mezclar en un aula equipos modernos y equipos antiguos que se conecten y aprovechen la potencia de los primeros, aprovechando mejor los recursos disponibles.

Para evitar cargar un entorno de escritorio completo usaremos una funcionalidad que tiene lightdm de permitir ejecutar un script antes de iniciar sesión, de tal manera que este script ("session-wrapper") puede sustituir a la sesión normal. Para ello, tal como cuentan aquí, definimos este session-wrapper en el fichero lightdm.conf:
# cat /etc/lightdm.conf
[Seat:*]
...
session-wrapper = /usr/local/bin/lightdm-session-wrapper
...
Como dicen la documentación de lightdm: "session-wrapper is a the command to run for a session. This command is run as the user and needs to exec the command passed in the arguments to complete running the session. Use this if you need to do special setup for a user session. Note the default is 'lightdm-session' so you should chain to this if you need to override this setting"

En el fichero lightdm-session-wrapper tenemos:
# cat /usr/local/bin/lightdm-session-wrapper
#!/bin/bash

case "$USER" in
    "remoto") /usr/local/bin/kiosko.sh ;;
    *) $@ ;;
esac
Expliquemos:
  • Si el que inicia la sesión es el usuario llamado "remoto" (al cual debe existir, claro está), no se carga ningún escritorio y se lanza el script indicado. Incluso podria ser el usuario por defecto haciendo autologin si así lo deseamos.
  • En cualquier otro caso, la caga del escritorio se realiza normalmente con el comando "$@".
El contenido de kiosko.sh sería:
# cat /usr/local/bin/kiosko.sh 
#! /bin/bash

equipo="pc-destino"
usuario="usuario1"
comando="rdesktop -u $usuario -f $equipo"

if ping -c 1 $equipo &> /dev/null
then
  while true
  do
       rdesktop -u "gm_primero_remoto" -f  "$equipo"
       if zenity  --width=400 --question --text='¿Quieres salir y volver a la pantalla de inicio?' --title='Logout'
       then
            exit
       fi
   done
else
     zenity --info --width=400 --text="Parece que el ordenador $equipo no está encendido y no puedo conectar con él. Enciéndelo y vuelve a intentarlo"
     exit
fi
Indicamos un equipo al que conectar, un usuario (no es obligatorio, podemos dejarlo en blanco. También podemos indicar una contraseña para que se haga login automático en el puesto remoto) y un comando de conexión. Asumo que conectamos a un Windows y por eso usamos "rdesktop". También podríamos usar "freerdp", que tiene fama de ser mas rápido y completo. En ese caso el comando sería algo así como:
comando="xfreerdp /f -compression -toggle-fullscreen /u:$usuario /v:$equipo"
Por todo lo demás, lo que se hace es verificar que el equipo remoto está encendido y si lo está conectar a él en un bucle infinito hasta que digamos que no queremos continuar.

Sobre el espinoso tema de realizar la conexión hacia un Windows nos podemos preguntar: ¿es posible conectar varios usuarios a la vez mediante Remote Desktop sin expulsar al usuario que está trabajando?. En teoría no, pero en la práctica si, con ayuda de la aplicación de parcheo llamada RDP Wrapper. Como cada cierto tiempo Microsoft mete una actualización que fastidia el parche, la usaremos en combinación con RDP Wrapper & Autoupdate, que añade una tarea programada en el arranque que actualiza el parche cada vez que Windows toca algo.

Por supuesto, para Linux no hay problema en conectar varios usuarios de forma concurrente con, por ejemplo, x2go.

La solución Fucking Master pasa por seleccionar la conexión remota desde el mismo GRUB, al arrancar el equipo. El sistema viene descrito aquí. Se basa en crear un servicio alternativo a lightdm que carga directamente sin preguntar nada la sesión que lanza la conexión remota. Dicho servicio alternativo se ejecuta desde el GRUB mediante el uso de los parámetros systemd.mask y systemd.wants. No lo he implantado porque previsiblemente sería demasiado complicado para algunos de mis usuarios.


Tras los exitosos litofrenados de la SN8 y la SN9, el 3 de marzo SpaceX lanza la Starship SN10 a intentar de nuevo su arriesgada maniobra: subir 12Km, dejarse caer planeando en horizontal y aterrizar en vertical.

Y ayer lo hicimos:



La maniobra de aterrizaje fue un poco brusca, lo que causó un escape de metano y una explosión posterior, cuando ya estaba posada. Eso convierte la SN10 en la primera Starship que ha volado dos veces. Nos lo cuenta mejor en este enlace el incomparable Daniel Marín.

jueves, 25 de febrero de 2021

Modificar aplicación Android para que no se actualice desde Google Play.

Uso una aplicación Android que controla mi pulsera inteligente de gama ultrabaja, Lefun Health. El problema que tengo es que la versión 1.2 es la que mejor funciona con mi dispositivo, que no parece tener obsolescencia programada. La versión actual es la 2.34 y aunque tenga desactivado manualmente en su configuración la actualización de la aplicación, lo cierto es que de vez en cuando me desobedece y se actualiza, hacíendome la puñeta al perder funcionalidades, ya que no es compatible 100% con mi obsoleta y pero funcional pulsera.

Buscando en Internet encontré la manera de impedir de forma permanente que la aplicación se actualice. El truco está en bajar el fichero .apk con la versión que nos interesa y firmarlo de nuevo a pelo, simulando que somos nosotros el creador del .apk. Tras instalar dicho .apk ya no encuentra relación entre lo que tenemos instalado y su versión del Google Play, por lo que no se actualiza más.

Los pasos son:
  • Descargamos .apk de la versión que nos interesa desde uptodown o apkpure, que son repositorios de confianza con todas las versiones de muchísimas aplicaciones android. En mi caso seria desde aquí
  • Abrimos el .apk desde el explorador de archivos y borramos la carpeta META-INF que contiene la firma digital del creador original.
  • Instalamos en nuestro Linux el paquete que contiene los comandos keytool y jarsigner, normalmente el que nos trae el runtime de java.
  • Creamos una clave de firma:
    # keytool -genkey -v -keystore lefun.keystore -alias lefun -keyalg RSA -keysize 2048 -validity 10000
  • Y firmamos con ella el .apk descargado:
    # jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore lefun.keystore Lefun\ Health_v1.2.0.apk lefun
  • Ahora metemos la nuevo .apk en el móvil (no olvidemos desinstalar la versión original de la aplicación previamente) y la instalamos manualmente. Habrá que activar los orígenes desconocidos y aceptar varios avisos, ya que el sistema nos amenaza con plagas y desdichas por instalar un apk con una firma tan rara.
  • Ya está. Si abrimos la aplicación en el Gestor de Aplicaciones veremos que no hay posibilidad de actualizarla, ya que no encuentra equivalente en la Play Store.

Con esto ya tenemos todo listo para que esta aplicación nos acompañe sin actualizarse mientras dure la pulsera, esperemos que muchos años más.




Ya estamos de nuevo en Marte. Sin palabras, o mejor con palabras de Daniel Marín.

Eso no es todo, amigos, la Tianwen-1 de China está orbitando Marte desde hace un par de de semanas, buscando un lugar para aterrizar su rover en mayo. Este año va a ser grande.

jueves, 21 de enero de 2021

Preparando un dispositivo USB con arranque clonezilla e imágenes para restauración.

Siempre se me olvidan los pasos y tengo que andar mirando notas cuando me hace falta, así que voy a dejar aquí apuntados los métodos que tengo de crear un disco duro externo o pendrive autoarrancable con clonezilla y varias imágenes de disco.

Usaré clonezilla-live-2.5.5-38-amd64, que es el estándar que usamos en los centros educativos. Bajamos la versión amd64 en fichero zip mediante este enlace.

Asumirémos que el dispositivo USB está en la ruta /dev/sdd (con el comando "fdisk -l" podemos averiguar cual es el /dev/sdX que usa una vez pinchado en el puerto USB). A la hora de organizarlo tenemos dos opciones.
  1. Una única partición: se crea con gparted del tipo FAT32 y luego se formatea con:
    # mkfs.vfat -F 32 /dev/sdd1
  2. Usar dos particiones: con gparted creamos una (sdd1) con FAT32 de 256Mb o un poco mayor. El resto de disco con una partición (sdd2) de tipo NTFS o ext3/4 a la que pondremos la etiqueta "IMAGENES" para poder identificarla y montarla luego fácilmente. Una vez creadas las formateamos con:
    # mkfs.vfat -F 32 /dev/sdd1 
    # mkfs.ext3 /dev/sdd2
    # tune2fs –L IMAGENES /dev/sdd2
    
    o bien
    # mkfs.vfat -F 32 /dev/sdd1 
    # mkfs.ntfs /dev/sdd2
    # ntfslabel /dev/sdd2 IMAGENES
¿Qué diferencia hay entre poner una o dos particiones? Bueno, si ponemos una partición esta debe ser FAT32 por narices, con los límites de este formato (ficheros como mucho de 4GB y tamaño máximo de partición de 2TB). Además esa partición se monta de forma automática como "solo lectura", por tanto podemos restaurar imágenes pero no guardarlas en el dispositivo.

Con dos particiones, al ser la segunda extX o NTFS no tiene dichas limitaciones y además como la montamos en modo rw se puede tanto restaurar como guardar imágenes. Que cada cual elija su método favorito.

Una vez tenemos el disco con una o dos particiones preparado, lo conectamos y descomprimimos en la primera partición el fichero zip antes descargado, quedando la siguiente estructura de directorios dentro del mismo:
.
├── boot
│   └── grub
│       ├── efiboot.img
│       └── grub.cfg
├── Clonezilla-Live-Version
├── EFI
│   └── boot
│       ├── bootia32.efi
│       ├── bootx64.efi
│       ├── grub.cfg
│       ├── ocswp-grub2.png
│       └── unicode.pf2
├── GPL
├── home
│   └── partimag
├── live
│   ├── Clonezilla-Live-Version
│   ├── filesystem.packages
│   ├── filesystem.packages-remove
│   ├── filesystem.size
│   ├── filesystem.squashfs
│   ├── freedos.img
│   ├── initrd.img
│   ├── ipxe.efi
│   ├── ipxe.lkn
│   ├── memtest
│   └── vmlinuz
├── syslinux
│   ├── chain.c32
│   ├── drblwp.png
│   ├── isolinux.bin
│   ├── isolinux.cfg
│   ├── iso_sort.txt
│   ├── ldlinux.c32
│   ├── libcom32.c32
│   ├── libutil.c32
│   ├── memdisk
│   ├── menu.c32
│   ├── ocswp.png
│   ├── syslinux.cfg
│   └── vesamenu.c32
└── utils
    ├── linux
    │   ├── extlinux
    │   ├── makeboot.sh
    │   ├── syslinux
    │   └── VERSION.txt
    ├── mbr
    │   └── mbr.bin
    ├── README.txt
    ├── win32
    │   ├── makeboot.bat
    │   ├── syslinux.exe
    │   └── VERSION.txt
    └── win64
        ├── makeboot64.bat
        ├── syslinux64.exe
        └── VERSION.txt

13 directories, 45 files
Ahora hay que configurarlo como autoarrancable, eso lo hacemos con los comandos:
# cd /ruta/pendrive/utils/linux
# bash makeboot.sh /dev/sdd1
Con esto, ya tenemos el disco arrancable. Ahora hay que meter la imágenes clonezilla (recordemos que cada imagen de disco va un un subdirectorio separado) que queremos usar:
  1. Si usamos una partición, metemos las imágenes en el directorio /home/partimag de sdd1.
  2. Si usamos dos particiones, metemos las imágenes en el directorio raíz de sdd2
Por defecto, al arrancar clonezilla la primera partición del pendrive se monta en modo solo-lectura sobre /lib/live/mount/medium. Cuando se carga el programa principal (ocs-live-general) se enlaza /home/partimag sobre /lib/live/mount/medium/home/partimag. De esta manera, si al llegar al menú de selección de /home/partimag le decimos que use el ya existente (opción Skip) tendremos acceso directo a las imágenes allí contenidas.


El problema es cuando nuestras imágenes están en una partición distinta a sdd1. Podemos usar la opción "local_dev" de la captura anterior para montar dicha partición a mano o bien indicar en el arranque que se monte automáticamente. Para esto último debemos editar el fichero grub.cfg. Este fichero se guarda en la ruta EFI/boot/grub.cfg de la primera partición. Allí tendremos una serie de entradas como:
menuentry "Clonezilla live (Default settings, VGA 800x600)"{
  search --set -f /live/vmlinuz
  linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset noeject locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch="no" vga=788 ip= net.ifnames=0  splash i915.blacklist=yes radeonhd.blacklist=yes nouveau.blacklist=yes vmwgfx.enable_fbdev=1
  initrd /live/initrd.img
}
En esa entrada habría que añadir el comando:
ocs_prerun="mount -L IMAGENES /home/partimag"
El cual, efectivamente, monta la partición etiquetada como IMAGENES en la ruta /home/partimag antes de entrar en el programa general de clonezilla que, como podemos ver, se llama "ocs_live_general".

El resultado final es:
menuentry "Clonezilla live (Default settings, VGA 800x600)"{
  search --set -f /live/vmlinuz
  linux /live/vmlinuz boot=live union=overlay username=user config components quiet noswap edd=on nomodeset noeject locales= keyboard-layouts= ocs_prerun="mount -L IMAGENES /home/partimag" ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch="no" vga=788 ip= net.ifnames=0  splash i915.blacklist=yes radeonhd.blacklist=yes nouveau.blacklist=yes vmwgfx.enable_fbdev=1
  initrd /live/initrd.img
}
De esta manera, al arrancar ya tendremos en /home/partimag las imágenes contenidas en la segunda partición del disco. Tan solo hay que escoger "Skip/Saltar: use existing /home/partimag" al llegar al llegar al menú de selección de /home/partimag.

Con esto tenemos resuelta la cuestión de tener un dispositivo que contenga tanto el clonezilla como las imágenes. Otra cosa que muchas veces se necesita es una opción que cargue una imagen de forma automática, sin intervención humana. En ese caso añadimos a EFI/boot/grub.cfg una opción como:
menuentry "Clonezilla restauracion automática imagen backup-dep"{
  search --set -f /live/vmlinuz
  linux /live/vmlinuz boot=live config components union=overlay noswap edd=on nomodeset noprompt ocs_prerun="mount -L IMAGENES /home/partimag" ocs_postrun="sudo shutdown -r now" ocs_live_run="ocs-live-restore" ocs_live_extra_param=" -e1 auto -e2 -r -j2 -scr -p true restoredisk backup-dep sda" keyboard-layouts="es" ocs_live_batch="yes" locales="es_ES.UTF-8" vga=791 ip=frommedia nosplash
  initrd /live/initrd.img
}
Que coge la imagen "backup-dep" desde la partición IMAGENES del dispositivo y la restaura en la unidad de disco duro /dev/sda, apagando el ordenador al finalizar. El truco está en:
ocs_live_run="ocs-live-restore" 
ocs_live_extra_param=" -e1 auto -e2 -r -j2 -scr -p true restoredisk backup-dep sda"}
Para cambiar el nombre de la imagen o el disco destino solo hay que modificar el fragmento "backup-dep sda". La explicación del resto de parámetros podemos encontrarla en este post del blog de mi compañero Esteban.


Seguimos para bingo con SpaceX, ya tenemos un Falcon 9 que ha sido utilizado 8 veces sin problemas. Esta vez ha aterrizado en la barcaza "Just Read The Instructions", con malas condiciones de viento y mar. Ha colocado en órbita otros 60 satélites Starlink, por lo que ya tenemos 1015 subidos para proporcionar Internet desde la órbita baja.
Según he leído en algún sitio, ya se han lanzado mas Falcon 9 que cualquier otro tipo de cohete en la historia. Y luego hay conspiranoicos que dicen que todo es CGI...

jueves, 7 de enero de 2021

Haciendo funcionar un dispositivo wifi USB Mediatek MT7601U en Linux.

Tengo un pincho USB wifi regalado que al probarlo no me funcionó en Linux. El sistema no creaba el dispositivo de red wifi al conectarlo. Su identificación USB es:
# lsusb | gre0 -i wireless
Bus 001 Device 003: ID 148f:7601 Ralink Technology, Corp. MT7601U Wireless Adapter
Lo curioso es que el driver mt7601u.ko si existe para Linux hace tiempo. En el caso de Manjaro tuve que instalarlo con:
$ pamac build  mt7601u-dkms-git
Una vez creado el módulo verifico que se carga en memoria:
# lsmod | grep mt
mt7601u               139264  0
mac80211             1110016  1 mt7601u
cfg80211              987136  2 mt7601u,mac80211
Pero nada, no funciona: no hay dispostivo wifi. Miro el log y veo estos errores recurrentemente:
   90.682395] mt7601u 1-1.1:1.0: ASIC revision: 76010001 MAC revision: 76010500
[   90.684079] mt7601u 1-1.1:1.0: Firmware Version: 0.1.00 Build: 7640 Build time: 201302052146____
[   91.066394] mt7601u 1-1.1:1.0: EEPROM ver:0c fae:00
[   91.232416] mt7601u 1-1.1:1.0: Error: MCU resp urb failed:-71
[   91.232420] mt7601u 1-1.1:1.0: Error: MCU resp evt:0 seq:5-4!
[   91.236894] mt7601u 1-1.1:1.0: Error: MCU resp urb failed:-71
[   91.236897] mt7601u 1-1.1:1.0: Error: MCU resp evt:0 seq:5-4!
[   91.241147] mt7601u 1-1.1:1.0: Error: MCU resp urb failed:-71
[   91.241150] mt7601u 1-1.1:1.0: Error: MCU resp evt:0 seq:5-4!
[   91.245395] mt7601u 1-1.1:1.0: Error: MCU resp urb failed:-71
[   91.245398] mt7601u 1-1.1:1.0: Error: MCU resp evt:0 seq:5-4!
[   91.249689] mt7601u 1-1.1:1.0: Error: MCU resp urb failed:-71
[   91.249692] mt7601u 1-1.1:1.0: Error: MCU resp evt:0 seq:5-4!
[   91.249694] mt7601u 1-1.1:1.0: Error: mt7601u_mcu_wait_resp timed out
[   91.439297] mt7601u 1-1.1:1.0: Vendor request req:07 off:0080 failed:-71
[   91.605971] mt7601u 1-1.1:1.0: Vendor request req:02 off:0080 failed:-71
[   91.772616] mt7601u 1-1.1:1.0: Vendor request req:02 off:0080 failed:-71
[   91.772671] mt7601u: probe of 1-1.1:1.0 failed with error -110
Buscando en Internet encuentro esta solución. Los pasos descritos son:

1) Descargamos el código fuente del driver:
# wget -O master.zip https://codeload.github.com/jeremyb31/mt7601u-5.3/zip/master
# unzip -x master.zip
2) Entramos en el directorio mt7601u-5.3-master y modificamos a mano el fichero phy.c en los lugares indicados:
En la función mt7601u_init_cal() comentar las líneas:
      // ret = mt7601u_mcu_calibrate(dev, MCU_CAL_RXIQ, 0);
      // if (ret)
      // return ret;
      // ret = mt7601u_mcu_calibrate(dev, MCU_CAL_DPD, dev->dpd_temp);
      // if (ret)
      // return ret;
En la función mt7601u_phy_recalibrate_after_assoc() comentar las línea:
      // mt7601u_mcu_calibrate(dev, MCU_CAL_DPD, dev->curr_temp);
3) Guardamos y compilamos para crear un fichero mt7601u.ko nuevo con los parches aplicados:
# make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
4) Una vez hecho esto quitamos el pincho del puerto USB y descargamos el driver de la memoria:
# rmmod mt7601u
5) Cargamos el módulo nuevo y pinchamos el USB otra vez:
# insmod ./mt7601u.ko
Ahora si que se detecta la tarjeta wifi:
# iwconfig
lo        no wireless extensions.

eno1      no wireless extensions.

wlp0s26u1u1  IEEE 802.11  ESSID:"WLAN_985A3D"  
          Mode:Managed  Frequency:2.462 GHz  Access Point: 78:81:02:02:AB:F1   
          Bit Rate=57.8 Mb/s   Tx-Power=20 dBm   
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:off
          Link Quality=49/70  Signal level=-61 dBm  
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:128  Invalid misc:1890   Missed beacon:0
Por último solo hay que ponerlo en la ruta del driver original, para que se cargue en cada arranque de forma automática. Para saber la ubicación adecuada del fichero, reiniciamos la máquina y hacemos:
# modinfo mt7601u | grep -i filename
filename:       /lib/modules/5.10.2-2-MANJARO/kernel/drivers/net/wireless/mt7601u.ko.xz
En mi caso la ruta es /lib/modules/5.10.2-2-MANJARO/kernel/drivers/net/wireless/mt7601u.ko.xz. Comovvemos, está ademas comprimido con formato .xz. Haremos:
# mv /lib/modules/5.10.2-2-MANJARO/kernel/drivers/net/wireless/mt7601u.ko.xz /lib/modules/5.10.2-2-MANJARO/kernel/drivers/net/wireless/mt7601u.ko.xz.bak
# cp mt7601u.ko /lib/modules/5.10.2-2-MANJARO/kernel/drivers/net/wireless/mt7601u.ko
# cd /lib/modules/5.10.2-2-MANJARO/kernel/drivers/net/wireless
# xz mt7601u.ko
Y listo, ya todo queda funcionando. Tan solo recordar que si actualizamos el kernel tendremos que repetir estos pasos para regenerar el driver parcheado.

miércoles, 16 de diciembre de 2020

Enredando con una ROM Rockchip RK3326 (II): examinando las particiones de la imagen.

Ya hemos desempaquetado la ROM en Enredando con una ROM Rockchip RK3326 (I). Vamos ahora a ver la estructura. Tenemos un directorio donde se ha descomprimido la imagen:
# tree Image/
└── Image
   ├── boot.img
   ├── kernel.img
   ├── kernel.img.krnl
   ├── MiniLoaderAll.bin
   ├── misc.img
   ├── oem.img
   ├── parameter.txt
   ├── parameter.txt.parm
   ├── recovery.img
   ├── resource.img
   ├── system.img
   ├── trust.img
   ├── uboot.img
   └── vendor.img
Veamos de que tipo es cada fichero:
# file ./Image/*
boot.img:           Android bootimg, kernel (0x10008000), ramdisk (0x11000000), second stage (0x10f00000), page size: 2048, cmdline (buildvariant=user)
kernel.img:         data
kernel.img.krnl:    data
MiniLoaderAll.bin:  data
misc.img:           data
oem.img:            Android sparse image, version: 1.0, Total of 32768 4096-byte output blocks in 6 input chunks.
parameter.txt:      ASCII text, with very long lines
parameter.txt.parm: Par archive data
recovery.img:       Android bootimg, kernel (0x10008000), ramdisk (0x11000000), second stage (0x10f00000), page size: 2048, cmdline (buildvariant=user)
resource.img:       data
system.img:         Android sparse image, version: 1.0, Total of 665600 4096-byte output blocks in 20 input chunks.
trust.img:          data
uboot.img:          data
vendor.img:         Android sparse image, version: 1.0, Total of 98304 4096-byte output blocks in 8 input chunks.
Hay un poco de todo:
  • Ficheros en formato "data" (que significa "no sé que carajos hay ahí dentro")
  • Ficheros en formato "Android bootimg"
  • Ficheros en formato "Android sparse image"
Todo estos ficheros se corresponden con distintas particiones del sistema Android, que en el caso de nuestra tablet serian realmente:
Partition Info(gpt):
NO  LBA        Size       Name
01  0x00004000 0x00002000 uboot
02  0x00006000 0x00002000 trust
03  0x00008000 0x00002000 misc
04  0x0000a000 0x00008000 resource
05  0x00012000 0x00010000 kernel
06  0x00022000 0x00010000 boot
07  0x00032000 0x00020000 recovery
08  0x00052000 0x00038000 backup
09  0x0008a000 0x00002000 security
10  0x0008c000 0x000c0000 cache
11  0x0014c000 0x00514000 system
12  0x00660000 0x00008000 metadata
13  0x00668000 0x000c0000 vendor
14  0x00728000 0x00040000 oem
15  0x00768000 0x00000400 frp
16  0x00768400 0x032d5bdf userdata
Las particiones anteriores las averiguamos entrando en "adb shell" y haciendo algo de esto.

Como se ve, casi todas las particiones reales de la tablet se corresponden con un fichero .img de los extraidos de update.img. Al actualizar el firmware con update.img lo que se hace realmente es desempaquetar y escribir estos ficheros sobre cada partición, ni más ni menos.

Vamos a husmear dentro de las particiones que podamos descomprimir, a ver que encontramos. Las "data" no las tocamos porque no sabemos como abrirlas.

1. Examinando particiones Android bootimg.

Empezamos por boot.img, que está en formato "Android bootimg":
# wget https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android-serialport-api/android_bootimg_tools.tar.gz
# tar xfvz android_bootimg_tools.tar.gz
-rwxrwxr-x cedric/cedric 12095 2011-10-31 20:54 mkbootimg
-rwxrwxr-x cedric/cedric 11936 2011-10-31 20:54 unpackbootimg
# mkdir boot
# ./unpackbootimg -i /ruta/al/fichero/boot.img -o boot
Con esto se desempaqueta el contenido al directorio boot. Veamos que hay dentro:
# file boot/*
boot.img-base:     ASCII text
boot.img-cmdline:  ASCII text
boot.img-pagesize: ASCII text
boot.img-ramdisk:  ASCII cpio archive (SVR4 with no CRC)
boot.img-zImage:   data
En los 3 primeros ficheros son de configuración, los dos últimos son imágenes comprimidas de ramdisk e imagen boot. Vamos a descomprimir el ramdisk, que contiene dentro un sistema de ficheros:
# mkdir ramdisk
# cd ramdisk
# zcat ../boot.img-ramdisk.gz | cpio -idmv
# cd ..
# tree ramdisk
ramdisk/
├── acct
├── bugreports -> /data/user_de/0/com.android.shell/files/bugreports
├── cache -> /data/cache
├── charger -> /sbin/charger
├── config
├── d -> /sys/kernel/debug
├── data
├── default.prop -> system/etc/prop.default
├── dev
├── drmboot.ko
├── etc -> /system/etc
├── fstab.rk30board
├── init
├── init.connectivity.rc
├── init.environ.rc
├── init.optee.rc
├── init.rc
├── init.rk30board.bootmode.emmc.rc
├── init.rk30board.bootmode.nvme.rc
├── init.rk30board.bootmode.unknown.rc
├── init.rk30board.environment.rc
├── init.rk30board.rc
├── init.rk30board.usb.rc
├── init.rk3326.rc
├── init.rockchip.rc
├── init.usb.configfs.rc
├── init.usb.rc
├── init.zygote32.rc
├── init.zygote64_32.rc
├── mnt
├── oem
├── proc
├── res
│   └── images
│       └── charger
│           ├── battery_fail.png
│           ├── battery_scale.png
│           └── font.png
├── rk30xxnand_ko.ko
├── sbin
│   ├── charger
│   ├── mkdosfs
│   ├── ueventd -> ../init
│   └── watchdogd -> ../init
├── sdcard -> /storage/self/primary
├── storage
├── sys
├── system
├── ueventd.rc
├── ueventd.rk30board.rc
├── vendor
└── verity_key
Como se ve, tiene un sistema de ficheros con sus script y procesos para el arranque del tablet. Si hacemos lo mismo con recovery.img veremos que hay un minisistema Linux con muchas cositas. Recordemos que el recovery es un Linux que muestra un menú con ciertas funciones básicas de formateo y poco más.
# mkdir recovery
# ./unpackbootimg -i /ruta/al/fichero/recovery.img -o recovery
# cd ramdisk-recovery
# zcat ../recovery.img-ramdisk.gz | cpio -idmv
# cd ..
# tree ramdisk-recovery
ramdisk-recovery/
├── acct
├── bugreports -> /data/user_de/0/com.android.shell/files/bugreports
├── charger -> /sbin/charger
├── config
├── d -> /sys/kernel/debug
├── data
│   └── misc
│       └── wifi
├── default.prop -> prop.default
├── dev
├── drmboot.ko
├── etc
│   ├── bluetooth
│   ├── firmware
│   ├── mke2fs.conf
│   └── recovery.fstab
├── fstab.rk30board
├── init
├── init.rc
├── init.rk30board.usb.rc
├── init.usb.configfs.rc
├── init.usb.rc
├── lib
│   └── firmware
├── mnt
.........................
.........................
.........................
│   │       └── wifi_efuse_8723ds.map
│   ├── firmware
│   └── lib
│       └── modules
│           └── wifi
│               ├── 8188eu.ko
│               ├── 8188fu.ko
│               ├── 8189es.ko
│               ├── 8189fs.ko
│               ├── 8723bs.ko
│               ├── 8723bu.ko
│               ├── 8723cs.ko
│               ├── 8723ds.ko
│               ├── 8822be.ko
│               └── bcmdhd.ko
└── verity_key
Bueno, pues esto no tiene más. Vamos a avanzar mirando más cosas.

2. Examinando particiones Android sparse image.

Vamos a las otras particiones, las "Android sparse image": oem, vendor y system, descritas de forma general aquí. El formato "sparse image" es un tipo de imagen comprimido con menos huecos en blanco para ocupar menos tamaño. Para trabajar con su contenido hay que descomprimir el archivo antes de nada, usando la utilidad "simg2img" que está en el paquete Ubuntu "android-tools-fsutils".
# apt-get install android-tools-fsutils
# mkdir system
# cd system
# simg2img /ruta/al/fichero/system.img  system-no-sparse.img
# file system-no-sparse.img
system-no-sparse.img: Linux rev 1.0 ext4 filesystem data, UUID=bd2ccb6e-a94e-53be-9c80-3f438b342f58, volume name "system" (extents) (64bit) (large files) (huge files)
El sistema ext4 es ya un sistema manejable que podemos montar con "mount -o loop":
# mount -t auto -o loop system-no-sparse.img  mnt
# tree -d mnt/
├── app
│   ├── BasicDreams
│   │   └── oat
│   │       └── arm64
│   ├── Bluetooth
│   │   ├── lib
│   │   │   └── arm64
│   │   └── oat
│   │       └── arm64
......................
......................
│   └── YouTube
│       └── oat
│           ├── arm
│           └── arm64
├── bin
│   └── hw
├── etc
│   ├── bluetooth
│   ├── init
│   ├── permissions
│   ├── ppp
│   ├── preferred-apps
│   ├── seccomp_policy
│   ├── security
│   │   └── cacerts
│   ├── selinux
│   │   └── mapping
│   └── sysconfig
├── fake-libs
├── fake-libs64
├── fonts
├── framework
│   ├── arm
│   ├── arm64
│   └── oat
│       ├── arm
│       └── arm64
├── lib
│   ├── drm
│   ├── hw
│   ├── modules
│   └── vndk-sp
├── lib64
│   ├── drm
│   ├── hw
│   └── vndk-sp
├── lost+found
├── media
│   └── audio
│       ├── alarms
│       ├── notifications
│       ├── ringtones
│       └── ui
├── priv-app
│   ├── BackupRestoreConfirmation
│   │   └── oat
│   │       └── arm64
......................
......................
│   │       └── arm64
│   └── WallpaperCropper
│       └── oat
│           └── arm64
├── tts
│   └── lang_pico
└── usr
    ├── hyphen-data
    ├── icu
    ├── idc
    ├── keychars
    ├── keylayout
    ├── share
    │   ├── bmd
    │   └── zoneinfo
    └── srec
        └── en-US
Esta partición como vemos contiene el sistema android completo, que se monta sobre /system en el arranque, con todas las aplicaciones por defecto y todo el entorno y comandos. Si queremos retocar el firmware del tablet será esta partición la que hay que modificar.

Las otras dos particiones son oem.img y vendor.img. Oem "incorporates OEM (Original Equipment Manufacturer i.e. hardware manufacturer or Mobile Phone brand) small customization (modifications) to original Android (AOSP) during OTA updates such as customized system properties values etc.". Esta partición se monta sobre la ruta /oem cuando el tablet está en funcionamiento. La convertimos y montamos como hicimos con system:
# mkdir oem
# cd oem
# simg2img /ruta/al/fichero/oem.img  oem-no-sparse.img
# file oem-no-sparse.img
oem-no-sparse.img: Linux rev 1.0 ext4 filesystem data, UUID=bd2ccb6e-a94e-53be-9c80-3f438b342f58, volume name "oem" (extents) (64bit) (large files) (huge files)
# mkdir mnt-oem
# mount -t auto -o loop oem-no-sparse.img  mnt-ome
# tree mnt-oem/
mnt-oem/
├── etc
│   ├── fs_config_dirs
│   ├── fs_config_files
│   └── package_performance.xml
└── lost+found
Para la partición vendor "is sister partition of /system on Android devices which holds system applications and libraries that do not have source code available on AOSP but added by vendors (OEM's). It also contains SoC firmware images i.e. hardware specific libraries and binaries (OpenGL, ISP...). Proprietary blobs (HALs) usually live in /vendor as shared libraries (.so files) which are loaded by Android binders when processes call a hardware component. This partition was optional before Treble support and /vendor used to be just a symlink to /system/vendor"..
# mkdir vendor
# cd vendor
# simg2img /ruta/al/fichero/vendor.img  vendor-no-sparse.img
# mount -t auto -o loop vendor-no-sparse.img  mnt
# tree -d mnt/
├── bin
│   └── hw
├── etc
│   ├── bluetooth
│   ├── firmware
│   ├── init
│   ├── permissions
│   ├── seccomp_policy
│   ├── selinux
│   ├── usb_modeswitch.d
│   └── wifi
├── lib
│   ├── egl
│   ├── hw
│   ├── mediacas
│   ├── mediadrm
│   ├── modules
│   │   └── wifi
│   ├── optee_armtz
│   ├── soundfx
│   └── vndk-sp
├── lib64
│   ├── egl
│   ├── hw
│   ├── mediacas
│   ├── mediadrm
│   ├── soundfx
│   └── vndk-sp
├── lost+found
├── media
└── overlay
    └── SysuiDarkTheme
Y ya está. Supongo que la mayoría de las imágenes de los distintos sistemas Android se desempaquetará y manejará de una menera similar.


La sonda Chang'e-5 recogió sus 2kg de rocas lunares (ya era hora desde los años 70), despegó y está de vuelta.



Hoy 16 de diciembre caerá en algún sitio de Mongolia Interior (que buen lugar para mandar a algunos que yo me sé). Como vemos, si hace falta irán a caballo a recuperarla donde aterrice.