domingo, 16 de septiembre de 2007

Autologin con cuenta root en sistema de consola

Resulta que necesitaba intentar re-compilar el kernel con unos pocos cambios para una materia de la facultad. Para no arriesgar mi instalación de Kubuntu cree una máquina virtual con VMware e instalé un Ubuntu Edgy que venía con Kernel 2.6.17-10, cercano al que teníamos que utilizar. Hice una instalación sin entorno gráfico, ni KDE ni Gnome, con la opción "Install a command-line system" utilizando el LiveCD de Edgy.

Después de reiniciar la máquina virtual setecientas veces cada vez que recompilaba e instalaba el nuevo kernel, me aburrí de loguearme como el usuario por defecto y tener que usar sudo para ejecutar cada comando privilegiado entrando el password cada vez. Así que me puse a investigar un poco.

Los pasos necesarios para activar la cuenta root (desactivada por defecto en *ubuntu), eliminar el usuario normal y hacer que se loguee con la cuenta de administrador por defecto son los siguientes:

Activamos la cuenta root para que se pueda hacer login con ella:

sudo passwd root



Se nos pide un nuevo password para esta cuenta, ya que el que usábamos era para la cuenta de usuario normal. Luego eliminamos la cuenta de usuario creada a la hora de la instalación junto con su directorio home (opción -r):

userdel -r nombre_usuario



Ahora vamos a necesitar mingetty, en lugar de getty, ya que éste permite hacer un login automático, evitando introducir el password. Editamos el archivo /etc/apt/sources.list (con sudo) descomentando los repositorios universe:

deb http://uy.archive.ubuntu.com/ubuntu/ edgy universe
deb-src http://uy.archive.ubuntu.com/ubuntu/ edgy universe



Y hacemos:

sudo apt-get update
sudo apt-get install mingetty



Con mingetty instalado, sólo nos queda editar el archivo /etc/event.d/tty1 (en el caso de que queramos que root se autologuee en esta terminal, aka Ctrl + Alt + F1), comentando la siguiente línea y agregando otra:

# respawn /sbin/getty 38400 tty1
respawn /sbin/mingetty --autologin root tty1



La proxima vez que reiniciemos, tendremos la sesión del usuario root ya iniciada.

Bueno, como verán cambié un poco la modalidad del blog con esta entrada, sin limitarme a escribir solamente sobre las cosas que entiendo... ;) Por otro lado se me hacía un estrés escribir ya que siempre intentaba explicar (y entender) cada paso que daba... Con suerte ahora pueden esperar entradas más seguido... :)

Saludos!

viernes, 14 de septiembre de 2007

Recuperando archivos corruptos

Muchas veces tenemos algún archivo en CD o DVD que no podemos copiar a disco porque cierta parte del mismo está corrupta. Tanto desde el escritorio en KDE como utilizando el comand cp en la consola el archivo se copia hasta el bloque en que no se puede leer y el resto se descarta.

No siempre que haya un defecto en el archivo éste queda inusable... en el caso de AVIs, MP3 y seguramente muchos otros el defecto simplemente se verá o se oirá por un instante y la reproducción continuará sin mayores problemas.

En Windows existen varios programas (ninguno libre, que yo sepa) que pueden recuperar estos archivos, obviamente rellenando las zonas que no se puedan leer. Estos procesos son generalmente poco configurables y muchas veces demoran demasiado por lo que uno termina aburriéndose y cancelándolos.

Resulta que googleando un pocó encontré que esto se puede hacer muy fácilmente con la añeja herramienta dd de Linux. Es muy fácil ajustar también el grado de recuperación/velocidad que se quiere usar.

El comando necesario es:

dd bs=2K if=ruta_archivo_entrada of=ruta_archivo_salida conv=noerror,sync



La herramienta dd se utiliza generalmente para hacer copias de dispositivos completos o también secciones importantes de éstos (como el MBR en el disco duro). En este caso vamos a utilizarla para copiar archivos reales de un sistema de archivos a otro. En realidad en Linux no hay diferencia: tanto un dispositivo como un archivo real en disco son en fin archivos en nuestro filesystem.

Este comando va a leer y escribir bloques de 2K = 2048 bytes de datos (si no se especifica bs se utiliza el valor por defecto de 512 bytes) desde el archivo origen (if) hacia el archivo destino (of). Sin opciones adicionales la copia se detiene al encontrar un error de lectura y el archivo queda truncado. Utlizando las opción conv=noerror,sync en la llamada se hace que cuando dd encuentra un error de lectura en un bloque particular el resto del bloque se rellene con carácteres nulos. Utilizando un tamaño de bloque más grande la copia se hace más rápidamente pero cada vez que se encuentra un error en un bloque, el resto del éste es ignorado. Bloques más pequeños minimizan la pérdida de datos, pero hacer la copia con un tamaño de bloque menor consume más tiempo.

Siempre que ocurra un error de lectura se imprime el mensaje de error correspondiente en stderr, junto con el estado actual del proceso. Por ejemplo:

dd: reading `/media/cdrom0/Se7en.1995.DVDRip.XviD-FreeGuy-CD1.avi': Input/output error
358586+704 records in
359290+0 records out
735825920 bytes (736 MB) copied, 532.963 seconds, 1.4 MB/s



El valor para 'records in' es la cantidad de bloques completos leidos con éxito seguida de la cantidad de bloques con error de lectura. El valor para 'records out', como ya suponen, es la cantidad de bloques escritos completos escritos exitosamente seguida de la cantidad de bloques en los que ocurrió un error de escritura. Estas cantidades corresponden con el tamaño de bloque bs especificado en la llamada al comando, como es fácil comprobar con la cantidad de bytes copiados desplegada.

También es posible hacer que dd imprima el estado actual (siempre en la salida estandar de error) en cualquier momento enviándole la señal SIGUSR1, de la siguiente manera:

$ kill -USR1 PID_DEL_PROCESO



Esto es útil para tener una idea sobre el avance de la copia si no han ocurrido errores.

Saludos!

domingo, 9 de septiembre de 2007

Renombramiento masivo de archivos

Sería interesante poder hacer un Find and Replace sobre los nombres de los archivos contenidos en un directorio, tan fácilmente como se hace por ejemplo desde KWrite. Para esto, podemos usar el siguiente comando:

for file in *; do mv $file `echo $file | \
           sed 's/TEX_ORIG/TEX_REEMP/g'`; done;



Lo que hace es iterar sobre los nombres de los archivos que matchean * (que podría ser también algo como *.avi o cualquier otra extensión) y ejecutar por cada uno de ellos una llamada a mv, renombrando el archivo de nombre $file (archivo actual de la iteración) por el resultado de hacer un reemplazo sobre el texto que lo compone utilizando sed.

También es posible el uso de expresiones regulares en general para hacer el reemplazo. De esta manera se puede matchear algún trozo de texto particular de $file y utilizarlo para formar el nombre de archivo destino. Por ejemplo:

$ ls
1.pdf 2.pdf 3.pdf 4.pdf 5.pdf 6.pdf 7.pdf 8.pdf 9.pdf

$ for file in *.pdf; do mv $file `echo $file | \
           sed 's/\^([0-9]*\).pdf/archivo-\1.pdf/g'`; done;

$ ls
archivo-1.pdf archivo-3.pdf archivo-5.pdf archivo-7.pdf archivo-9.pdf
archivo-2.pdf archivo-4.pdf archivo-6.pdf archivo-8.pdf



En este caso estamos utilizando grupos, delimitándolos con \( y \). De esta manera los nombres de archivo que consistan en una tira de dígitos (que es almacenada en \1) seguida por '.pdf', van a ser renombrados a 'archivo-' + tira_de_dígitos + '.pdf'.

Nótese que si el texto en $file no matchea la expresión regular, se estaría intentando mover un archivo con igual destino y origen (sed no modifica el texto de entrada), lo que causaría que se despliegue algo como 'mv: `X.pdf' and `X.pdf' are the same file' en pantalla. De todas formas, esto no afecta al renombramiento del resto de los archivos.

jueves, 6 de septiembre de 2007

Correr comando como root sin autenticarse

Si tenemos algún comando con permiso de ejecución exclusivo para root que ejecutemos muy a menudo o que se ejecute de forma automática (ej.: un theme de Superkaramba), podemos hacer que no se nos pida autenticarnos cada vez que queramos utilizarlo.

Es el caso del script de la entrada anterior, que necesitaba ejecutarse como sudo script.sh ya que éste utiliza la herramienta apt-get con argumento upgrade, para la cual sólo el usuario root tiene permisos de ejecución.

El comando sudo en las distros Debian/*ubuntu nos permite ejecutar un comando como otro usuario del sistema, en particular root, según los permisos en /etc/sudoers. Para esto puede estar configurado que se nos pida autenticarnos (introducir el password de nuestro usuario). Por defecto, en *ubuntu al menos, nuestro usuario tiene permiso para ejecutar cualquier comando como si fueramos root, siempre y cuando nos autentiquemos. Por esto vale aclarar que cuando hacemos sudo comando y escribimos la contraseña, estamos utilizando la contraseña de nuestro usuario y no la del usuario root (el login de root está desactivado por defecto, y por lo tanto no tiene contraseña).

Así, procedemos a editar /etc/sudoers utilizando el comando visudo (sin argumentos, pero como root), el cual chequeará la validez del archivo una vez terminada la edición. En nuestro caso, nos interesa agregar la siguiente línea al final del archivo:

usuario ALL = NOPASSWD: /ruta/al/comando



Con ella permitimos al usuario usuario ejecutar en cualquier host (útil sólo para sistemas distribuidos), sin que se le pida autenticarse, el comando /ruta/al/comando.

Para ejecutar el comando, el cual tiene permisos de ejecución para root solamente, debemos llamarlo con sudo y especificando la ruta al comando exactamente como la escribimos en /etc/sudoers. O sea:

sudo /ruta/al/comando



De esta forma la contraseña no nos será pedida y la ejecución del comando se hará instantáneamente.

miércoles, 5 de septiembre de 2007

Desplegar actualizaciones pendientes (apt-get)

La idea era crear un theme de Superkaramba que mostrara la cantidad de actualizaciones pendientes, cuáles son y cuánto pesan. Para esto como mínimo necesitamos un script que obtenga la información que queremos. Una forma es utilizando apt-get con el argumento upgrade. La salida es algo como:

neoakiraz@fcksys:~/pruebas$ sudo apt-get upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be upgraded:
  compiz compiz-bcop compiz-core compiz-dev compiz-fusion-plugins-extra
  compiz-fusion-plugins-main compiz-fusion-plugins-unofficial compiz-gnome
  compiz-kde compiz-plugins compizconfig-settings-manager emerald
  libcompizconfig-backend-gconf libcompizconfig0 libcompizconfig0-dev
  libdecoration0 libdecoration0-dev libemeraldengine0 libkadm55
  libkrb5-dev libkrb53 python-compizconfig
22 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 5909kB of archives.
After unpacking 106kB of additional disk space will be used.
Do you want to continue [Y/n]?



Lo que nos interesa obtener es la cantidad de paquetes actualizables, cuales son y el peso de la descarga. Para eso usamos este script:

#!/bin/bash
# ejecutamos el comando:
TEXTO=`echo "n" | apt-get upgrade`

# extraemos la cantidad de paquetes, cantidad a bajar y cuales son
PAQUETES_CANT=`echo $TEXTO | sed -r 's/.* ([0-9]*) upgraded.*/\1/'`
PAQUETES_SIZE=`echo $TEXTO | \
               sed -r 's/.*get ([0-9]*\.?[0-9]*[kM]B).*/\1/'`
PAQUETES=`echo $TEXTO | sed -r 's/.*: (.*) [0-9]* upgraded.*/\1/'`

# imprimimos
echo "Hay $PAQUETES_CANT actualizaciones, $PAQUETES_SIZE:"
echo $PAQUETES



Le damos permiso de ejecución para el nuestro usuario con chmod u+x script.sh y lo ejecutamos con ./script.sh.

Al ejecutar el comando, el cual espera un Y/n para saber si llevar a cabo la actualización, utilizamos un pipe (|) para conectarle una entrada estandar que contenga la letra n, ya que lo único que nos interesa es la información que despliega. De este modo no va a quedarse esperando una entrada del teclado.

Al guardar la salida en una variable, por algún motivo los \n (caracteres newline) no quedan almacenados. De esta forma el contenido de $TEXTO es una sola línea de texto, y sed la va a procesar como tal.

Utlizamos sed con la opción r para poder escribir expresiones regulares extendidas, que por ejemplo permite el uso de grupos (o "átomos"). Luego, entre comillas simples, va la instrucción a ejecutar sobre cada línea de texto de entrada. En este caso s/TEXTO1/TEXTO2/ reemplaza todas las ocurrencias de TEXTO1 por TEXTO2. La diferencia en este caso es que en lugar de reemplazar una simple cadena de texto por otro, utilizamos expresiones regulares (originales de perl). Por más información pasar por acá.

Finalmente imprimimos el contenido de las variables del modo que queramos. La salida en este caso:

Hay 22 actualizaciones, 5909kB:
compiz compiz-bcop compiz-core compiz-dev compiz-fusion-plugins-extra compiz-fusion-plugins-main compiz-fusion-plugins-unofficial compiz-gnome compiz-kde compiz-plugins compizconfig-settings-manager emerald libcompizconfig-backend-gconf libcompizconfig0 libcompizconfig0-dev libdecoration0 libdecoration0-dev libemeraldengine0 libkadm55 libkrb5-dev libkrb53 python-compizconfig



Como comentario final, notar que es necesario ejecutar el script con privilegios de root; en Debian/*ubuntu utilizando sudo.
Si lo vamos a usar en un theme de Superkaramba esto no es conveniente, por lo que podemos usar el método descrito en la siguiente entrada del blog. Para esto, previamente cambiamos el owner del archivo a root, con sudo chown root script.sh. De esta forma, el script queda con permisos de ejecución exclusivo para root.

Dudas, comentarios, sugerencias, p**eadas están bienvenidas! Saludos ;)

Listado cronológico de paquetes instalados/actualizados (dpkg)

Buenas! Arranco con mi blog de una buena vez, ya hace meses que estoy en la vuelta. La idea principal es guardarlo como diario de soluciones a problemas que se me plantean utilizando Linux. Soy relativamente nuevo en ello, me pasé de forma definitiva desde Güindous hace unos pocos meses, y la idea es loguear lo que voy aprendiendo para tenerlo como referencia, para que con suerte le sirva a alguien más y siempre escuchando críticas y sugerencias sobre formas más simples o eficentes de hacer lo mismo. Así que ya saben, comentarios bienvenidos!

Hoy me fue necesario ver los últimos paquetes que habían sido instalados/actualizados en mi Kubuntu... Googleando, encontré aquí cómo hacerlo. Módificando un poco el comando llegué al siguiente, que despliega todos los paquetes que fueron instalados/actualizados en el sistema en orden cronológico:

ls /var/lib/dpkg/info/ -ltr | grep .list$ | awk '{print $6" "$7" "$8 }' | sed 's/\.list//'



En el directorio /var/lib/dpkg/info/parecen guardarse varios tipos de archivo referentes a los paquetes manejados por el gestor de Debian/*ubuntu, dpkg. En particular, los .list tienen información sobre los archivos que fueron copiados para cada paquete, y en que ubicación están. De todas formas lo que usamos no es esto sino la fecha de creación/modificación de estos archivos, la cual coincide con la fecha de instalación/actualización del paquete, y su nombre.

Así listamos en formato largo (l) el contenido del directorio especificado ordenando en reverso (r) según la fecha de modificación (t) con ls. Nos quedamos con las líneas que terminan en ".list" (para tener una sola entrada por paquete) utilizando grep. Imprimimos los campos de cada línea según el formato que especifiquemos con awk y eliminamos el ".list" del final de cada línea para quedarnos sólo con el nombre del paquete con sed.

La salida quedaría algo como:

...
2007-09-01 12:43 zenity
2007-09-01 12:43 ubuntu-desktop
2007-09-01 12:43 brltty-x11
2007-09-01 14:12 compiz-gnome
2007-09-02 14:42 libwrap0
2007-09-02 14:42 linux-headers-2.6.20-16
2007-09-02 14:43 linux-headers-2.6.20-16-generic
2007-09-02 14:43 linux-image-2.6.20-16-generic
2007-09-02 14:43 tcpd
2007-09-02 14:43 linux-libc-dev