Recuperar el nombre de archivo del descriptor de archivo en C

105

¿Es posible obtener el nombre de archivo de un descriptor de archivo (Linux) en C?

adk
fuente
Supongo que la respuesta elegida se debe dar a zneak ya que su solución tiene una mejor portabilidad y no tiene problemas de acceso notados.
Sergei
No es compatible con Ubuntu 14.04 (kernel 3.16.0-76-generic). Supongo que no es compatible con Linux en absoluto.
felipou
Para macOS, consulte esta respuesta a otra pregunta de D.Nathanael .
Jonathan Leffler

Respuestas:

120

Se puede utilizar readlinken /proc/self/fd/NNNdonde NNN es el descriptor de archivo. Esto le dará el nombre del archivo tal como estaba cuando se abrió; sin embargo, si el archivo se movió o eliminó desde entonces, es posible que ya no sea exacto (aunque Linux puede rastrear los cambios de nombre en algunos casos). Para verificarlo, statel nombre de archivo dado y fstatel DF que tiene, y asegúrese st_devy st_inoson los mismos.

Por supuesto, no todos los descriptores de archivo se refieren a archivos, y para ellos verá algunas cadenas de texto extrañas, como pipe:[1538488]. Dado que todos los nombres de archivo reales serán rutas absolutas, puede determinar cuáles son con bastante facilidad. Además, como han señalado otros, los archivos pueden tener varios vínculos físicos que los apunten; esto solo informará con el que se abrió. Si desea encontrar todos los nombres de un archivo determinado, solo tendrá que recorrer todo el sistema de archivos.

bdonlan
fuente
9
Siempre que el archivo original todavía tenga referencias a él (una apertura fdsería dicha referencia), el número de inodo no se puede reutilizar. Cualquier software que utilice un número de inodo después de cerrar el archivo o antes de abrirlo está inherentemente sujeto a condiciones de carrera.
R .. GitHub DEJA DE AYUDAR A ICE
3
¡Peligro, Will Robinson! Esto no siempre funciona --- si hace setuid()trucos, es posible /proc/self/fdque su proceso no pueda acceder a él. Ver: permalink.gmane.org/gmane.linux.kernel/1302546
David dado
2
@bdonlan: ¿y en el caso de que / proc no esté montado?
user2284570
1
@ user2284570, esta respuesta es específica de Linux. No sé si NetBSD admite procfs en absoluto; si su host compartido no lo proporciona, probablemente sea porque NetBSD no lo admite en absoluto y utiliza otro mecanismo en su lugar. Es posible que desee publicar otra pregunta con un enfoque de NetBSD para ver si alguien sabe cómo NetBSD expone esta información (es posible que desee probar también la respuesta de zneak a continuación, OS X es más similar a BSD que a Linux)
bdonlan
1
@bdonlan: NetBSD support / proc pero no es obligatorio montarlo. Cada vez que mencioné, la respuesta fue "cambie a un proveedor de mayor costo y obtendrá / proc". Así que estoy buscando una solución sin proceso.
user2284570
90

Tuve este problema en Mac OS X. No tenemos un /procsistema de archivos virtual, por lo que la solución aceptada no puede funcionar.

En cambio, tenemos un F_GETPATHcomando para fcntl:

 F_GETPATH          Get the path of the file descriptor Fildes.  The argu-
                    ment must be a buffer of size MAXPATHLEN or greater.

Entonces, para obtener el archivo asociado a un descriptor de archivo, puede usar este fragmento:

#include <sys/syslimits.h>
#include <fcntl.h>

char filePath[PATH_MAX];
if (fcntl(fd, F_GETPATH, filePath) != -1)
{
    // do something with the file path
}

Como nunca recuerdo dónde MAXPATHLENestá definido, pensé que PATH_MAXdesde syslimits estaría bien.

zneak
fuente
@uchuugaka, probablemente no. Utilice getsockname.
zneak
2
¿Qué esperas? A menos que sea un socket UNIX, no tiene ningún archivo asociado.
zneak
2
@uchuugaka Sí, todo es un archivo, pero no todo es una entrada de directorio con un nombre y una ubicación dentro del árbol del sistema de archivos. Un archivo está representado por un inodo, puede existir sin ninguna entrada de directorio que se refiera a él.
lgeorget
9
En <sys / param.h>: #define MAXPATHLEN PATH_MAX
geowar
1
Acabo de probar esto y sigue siendo correcto si el archivo se mueve y lo vuelve a llamar (es decir, obtiene la nueva ruta del archivo). Sin embargo, esto no es compatible con Linux (probado en Ubuntu 14.04 - F_GETPATH ​​no está definido).
felipou
27

En Windows, con GetFileInformationByHandleEx , pasando FileNameInfo , puede recuperar el nombre del archivo.

Martin contra Löwis
fuente
17
Por mucho que deteste las ventanas, siempre es bueno tener el equivalente de Windows
Matt Joiner
15

Como señala Tyler, no hay forma de hacer lo que necesita "directa y confiablemente", ya que un FD dado puede corresponder a 0 nombres de archivo (en varios casos) o> 1 (múltiples "enlaces duros" es como se describe generalmente esta última situación ). Si aún necesita la funcionalidad con todas las limitaciones (en velocidad Y en la posibilidad de obtener 0, 2, ... resultados en lugar de 1), así es como puede hacerlo: primero, fstat el FD - esto le dice , en el resultado struct stat, en qué dispositivo vive el archivo, cuántos enlaces físicos tiene, si es un archivo especial, etc. Esto ya puede responder a su pregunta, por ejemplo, si hay 0 enlaces físicos, SABERÁ que de hecho no hay un nombre de archivo correspondiente. en disco.

Si las estadísticas le dan esperanza, entonces tiene que "recorrer el árbol" de directorios en el dispositivo relevante hasta que encuentre todos los enlaces físicos (o solo el primero, si no necesita más de uno y cualquiera servirá ). Para ese propósito, usa readdir (y opendir & c, por supuesto) abriendo subdirectorios de manera recursiva hasta que encuentre en un struct direntasí recibido el mismo número de inodo que tenía en el original struct stat(en ese momento, si desea la ruta completa, en lugar de solo el nombre, necesitará caminar la cadena de directorios hacia atrás para reconstruirlo).

Si este enfoque general es aceptable, pero necesita un código C más detallado, avísenos, no será difícil de escribir (aunque prefiero no escribirlo si es inútil, es decir, no puede soportar el rendimiento inevitablemente lento o el posibilidad de obtener! = 1 resultado para los propósitos de su aplicación ;-).

Alex Martelli
fuente
9

Antes de descartar esto como imposible, le sugiero que mire el código fuente del comando lsof .

Puede haber restricciones, pero lsof parece capaz de determinar el descriptor y el nombre del archivo. Esta información existe en el sistema de archivos / proc, por lo que debería ser posible acceder a ella desde su programa.

Pato
fuente
6

Puede usar fstat () para obtener el inodo del archivo por struct stat. Luego, usando readdir () puede comparar el inodo que encontró con los que existen (struct dirent) en un directorio (asumiendo que conoce el directorio, de lo contrario tendrá que buscar en todo el sistema de archivos) y encontrar el nombre de archivo correspondiente. ¿Asqueroso?

PetrosB
fuente
2

Imposible. Un descriptor de archivo puede tener varios nombres en el sistema de archivos o puede que no tenga ningún nombre.

Editar: Suponiendo que está hablando de un sistema POSIX antiguo, sin ninguna API específica del sistema operativo, ya que no especificó un sistema operativo.

Tyler McHenry
fuente
4
entonces se aplica mi respuesta. Linux no tiene instalaciones para hacer esto. Los descriptores de archivos de Linux (POSIX) no se refieren necesariamente a archivos, e incluso si lo hacen, se refieren a inodos, no a nombres de archivos. Un descriptor puede apuntar a un archivo eliminado (que por lo tanto no tiene nombre, esta es una forma común de crear archivos temporales) o puede apuntar a un inodo con varios nombres (enlaces físicos).
Tyler McHenry
3
Intente echar un vistazo al código fuente de lsof. :) Eso es lo que hice cuando tuve la misma pregunta hace un tiempo. lsof trabaja con magia negra y cabras sacrificadas; no se puede esperar duplicar su comportamiento. Para ser más específicos, lsof está estrechamente acoplado con el kernel de Linux y no hace lo que hace por medio de ninguna API que esté disponible para el código de la tierra del usuario.
Tyler McHenry
27
Linux tiene una API de proceso no portátil para esto. De hecho, existen limitaciones, pero decir que es imposible es simplemente falso.
bdonlan
1
@Tyler: lsof se ejecuta en el espacio de usuario. Por lo tanto, hay una API para todo lo que hace disponible para el código de usuario :)
bdonlan
1
@Duck, la portabilidad probablemente sea la razón por la que la fuente de lsof tiene tanta magia negra; cada variante de UNIX lo hace de manera diferente. Las interfaces linux proc no son tan malas, en realidad, alebit está bastante escasamente documentado.
bdonlan