Ядро думает о каталогах не так, как вы - оно думает в терминах номеров инодов. Он хранит запись номера индекса (и номера устройства) для каталога, и это все, что ему нужно в качестве текущего каталога. Тот факт, что вы иногда указываете ему имя, означает, что он идет и отслеживает номер инода, соответствующий этому имени, но сохраняет только номер инода, потому что это все, что ему нужно.
Итак, вам придется кодировать подходящую функцию. Вы можете открыть каталог непосредственно с помощью open()
точно, чтобы получить дескриптор файла, который может использоваться fchdir()
; во многих современных системах с этим ничего нельзя сделать. Вы также можете не открыть текущий каталог; Вы должны проверить этот результат. Обстоятельства, при которых это происходит, редки, но не существуют. (Программа SUID может chdir()
перейти в каталог, который разрешены привилегиями SUID, но затем отбросить привилегии SUID, и процесс не сможет прочитать каталог; при таких обстоятельствах вызов getcwd()
также не будет выполнен, поэтому необходимо проверить, что Тоже!) Кроме того, если каталог удален, а ваш (возможно, длительный) процесс имеет его открытым, то последующий getcwd()
завершится неудачей.
Всегда проверяйте результаты системных вызовов; обычно есть обстоятельства, когда они могут потерпеть неудачу, даже если для них это ужасно неудобно. Есть исключения - getpid()
является каноническим примером - но их мало и они далеко друг от друга. (ОК: не все, что далеко между - getppid()
- другой пример, и это чертовски близко к getpid()
в руководстве; и getuid()
и родственники также не за горами в руководстве.)
Многопоточные приложения являются проблемой; использование chdir()
не очень хорошая идея в тех. Возможно, вам придется fork()
и попросить ребенка оценить имя каталога, а затем каким-то образом сообщить об этом родителю.
Бигноз спрашивает:
Это интересно, но, похоже, идет вразрез с сообщенным опытом кверента: что getcwd знает, как получить путь от fd. Это означает, что система знает, как перейти от fd к пути, по крайней мере, в некоторых ситуациях; Вы можете отредактировать свой ответ, чтобы решить эту проблему?
Для этого полезно понять, как - или хотя бы один механизм, с помощью которого - можно написать функцию getcwd()
. Игнорирование вопроса «нет разрешения», основной механизм, с помощью которого он работает:
- Используйте stat в корневом каталоге '/' (чтобы вы знали, когда прекратить движение вверх).
- Использовать stat для текущего каталога '.' (чтобы вы знали, где вы находитесь); это дает вам текущий индекс.
- Пока вы не достигнете корневого каталога:
- Сканируйте родительский каталог '..', пока не найдете запись с тем же индексом, что и текущий индекс; это даст вам имя следующего компонента пути к каталогу.
- А затем измените текущий индекс на индекс '.' в родительском каталоге.
- Когда вы достигнете root, вы можете построить путь.
Вот реализация этого алгоритма. Это старый код (изначально 1986 г .; последние не косметические изменения были в 1998 г.), и он не использует fchdir()
, как следует. Это также работает ужасно, если у вас есть автоматически смонтированные файловые системы NFS, поэтому я больше этим не пользуюсь. Однако это примерно эквивалентно базовой схеме, используемой getcwd()
. (Ох, я вижу строку из 18 символов ("../123456789.abcd") - ну, когда она была написана, машины, на которых я работал, имели только очень старые имена из 14 символов, а не современные flex-имена. Как я уже сказал, это старый код! Я не видел ни одной из этих файловых систем примерно через 15 лет, может быть, дольше. Есть также код, который можно связать с более длинными именами. Будьте осторожны при использовании этого.)
/*
@(#)File: $RCSfile: getpwd.c,v $
@(#)Version: $Revision: 2.5 $
@(#)Last changed: $Date: 2008/02/11 08:44:50 $
@(#)Purpose: Evaluate present working directory
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1987-91,1997-98,2005,2008
@(#)Product: :PRODUCT:
*/
/*TABSTOP=4*/
#define _POSIX_SOURCE 1
#include "getpwd.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_POSIX_SOURCE) || defined(USG_DIRENT)
#include "dirent.h"
#elif defined(BSD_DIRENT)
#include <sys/dir.h>
#define dirent direct
#else
What type of directory handling do you have?
#endif
#define DIRSIZ 256
typedef struct stat Stat;
static Stat root;
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_getpwd_c[] = "@(#)$Id: getpwd.c,v 2.5 2008/02/11 08:44:50 jleffler Exp $";
#endif /* lint */
/* -- Routine: inode_number */
static ino_t inode_number(char *path, char *name)
{
ino_t inode;
Stat st;
char buff[DIRSIZ + 6];
strcpy(buff, path);
strcat(buff, "/");
strcat(buff, name);
if (stat(buff, &st))
inode = 0;
else
inode = st.st_ino;
return(inode);
}
/*
-- Routine: finddir
Purpose: Find name of present working directory
Given:
In: Inode of current directory
In: Device for current directory
Out: pathname of current directory
In: Length of buffer for pathname
Maintenance Log
---------------
10/11/86 JL Original version stabilised
25/09/88 JL Rewritten to use opendir/readdir/closedir
25/09/90 JL Modified to pay attention to length
10/11/98 JL Convert to prototypes
*/
static int finddir(ino_t inode, dev_t device, char *path, size_t plen)
{
register char *src;
register char *dst;
char *end;
DIR *dp;
struct dirent *d_entry;
Stat dotdot;
Stat file;
ino_t d_inode;
int status;
static char name[] = "../123456789.abcd";
char d_name[DIRSIZ + 1];
if (stat("..", &dotdot) || (dp = opendir("..")) == 0)
return(-1);
/* Skip over "." and ".." */
if ((d_entry = readdir(dp)) == 0 ||
(d_entry = readdir(dp)) == 0)
{
/* Should never happen */
closedir(dp);
return(-1);
}
status = 1;
while (status)
{
if ((d_entry = readdir(dp)) == 0)
{
/* Got to end of directory without finding what we wanted */
/* Probably a corrupt file system */
closedir(dp);
return(-1);
}
else if ((d_inode = inode_number("..", d_entry->d_name)) != 0 &&
(dotdot.st_dev != device))
{
/* Mounted file system */
dst = &name[3];
src = d_entry->d_name;
while ((*dst++ = *src++) != '\0')
;
if (stat(name, &file))
{
/* Can't stat this file */
continue;
}
status = (file.st_ino != inode || file.st_dev != device);
}
else
{
/* Ordinary directory hierarchy */
status = (d_inode != inode);
}
}
strncpy(d_name, d_entry->d_name, DIRSIZ);
closedir(dp);
/**
** NB: we have closed the directory we are reading before we move out of it.
** This means that we should only be using one extra file descriptor.
** It also means that the space d_entry points to is now invalid.
*/
src = d_name;
dst = path;
end = path + plen;
if (dotdot.st_ino == root.st_ino && dotdot.st_dev == root.st_dev)
{
/* Found root */
status = 0;
if (dst < end)
*dst++ = '/';
while (dst < end && (*dst++ = *src++) != '\0')
;
}
else if (chdir(".."))
status = -1;
else
{
/* RECURSE */
status = finddir(dotdot.st_ino, dotdot.st_dev, path, plen);
(void)chdir(d_name); /* We've been here before */
if (status == 0)
{
while (*dst)
dst++;
if (dst < end)
*dst++ = '/';
while (dst < end && (*dst++ = *src++) != '\0')
;
}
}
if (dst >= end)
status = -1;
return(status);
}
/*
-- Routine: getpwd
Purpose: Evaluate name of current directory
Maintenance Log
---------------
10/11/86 JL Original version stabilised
25/09/88 JL Short circuit if pwd = /
25/09/90 JL Revise interface; check length
10/11/98 JL Convert to prototypes
Known Bugs
----------
1. Uses chdir() and could possibly get lost in some other directory
2. Can be very slow on NFS with automounts enabled.
*/
char *getpwd(char *pwd, size_t plen)
{
int status;
Stat here;
if (pwd == 0)
pwd = malloc(plen);
if (pwd == 0)
return (pwd);
if (stat("/", &root) || stat(".", &here))
status = -1;
else if (root.st_ino == here.st_ino && root.st_dev == here.st_dev)
{
strcpy(pwd, "/");
status = 0;
}
else
status = finddir(here.st_ino, here.st_dev, pwd, plen);
if (status != 0)
pwd = 0;
return (pwd);
}
#ifdef TEST
#include <stdio.h>
/*
-- Routine: main
Purpose: Test getpwd()
Maintenance Log
---------------
10/11/86 JL Original version stabilised
25/09/90 JL Modified interface; use GETCWD to check result
*/
int main(void)
{
char pwd[512];
int pwd_len;
if (getpwd(pwd, sizeof(pwd)) == 0)
printf("GETPWD failed to evaluate pwd\n");
else
printf("GETPWD: %s\n", pwd);
if (getcwd(pwd, sizeof(pwd)) == 0)
printf("GETCWD failed to evaluate pwd\n");
else
printf("GETCWD: %s\n", pwd);
pwd_len = strlen(pwd);
if (getpwd(pwd, pwd_len - 1) == 0)
printf("GETPWD failed to evaluate pwd (buffer is 1 char short)\n");
else
printf("GETPWD: %s (but should have failed!!!)\n", pwd);
return(0);
}
#endif /* TEST */