Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Implementierung von getcwd

10 views
Skip to first unread message

Fabian Wickborn

unread,
Dec 19, 2001, 4:47:08 AM12/19/01
to
Hallo zusammen!

Im Rahmen meines Studiums soll ich eine Unixfunktion implementieren, welche
das Workingdirectory des aktuellen Prozesses zusammenstellt - so wie
getcwd(3), diese schon vorhandene POSIX.1-Funktion soll aber aus
didaktischen Gruenden nicht verwendet werden.

So weit, so gut. Den Algorithmus habe ich im Stevens ("Adv. Prog. in the
UNIX Env.") gefunden: Eine Funktion, die im aktuellen Verzeichnis
(".") beginnt und sich in der Verzeichnishierarchie nach oben arbeitet
(und dabei ".." benutzt, um ein Ebene hoeher zu kommen). In jedem
Verzeichnis liest er die Verzeichniseintraege bis er den Namen findet,
der zu dem I-Node des Verzeichnisses gehoert, aus dem die Funktion
gerade kommt. Wiederholt man diese Prozedur, bis das Wurzelverzeichnis
erreicht ist (".." = "."), ergibt sich der gesamte absolute Pfadname
des aktuellen Arbeitsverzeichnisses.

Diese Funktion habe ich folgendermassen implementiert (die API stimmt dabei
nicht mit getcwd(3) ueberein, dies ist auch nicht zwingend noetig):
char *workingdir() {
char *pwdstring=NULL,*oldpath;
struct stat currentstat, parentstat;
struct dirent *direntry;
DIR *parentdir;

while(1) {

stat(".",&currentstat);
stat("..",&parentstat);

if ((currentstat.st_dev == parentstat.st_dev) &&
(currentstat.st_ino == parentstat.st_ino)) {
return pwdstring;
}
else {
parentdir=opendir("..");
while ((direntry = readdir(parentdir)) && (
direntry->d_ino != currentstat.st_ino));
/* Evtl. noch testen: d_name ungleich "." oder ".." */


/* Verz-String um direntry->d_name erweitern */
chdir("..");
}
}
}

Den Test auf gleiches Device in der ersten if-Anweisung habe ich
hinzugefuegt, als ich beim Testen auf meinem Linux-System bemerkte,
dass er sonst nur bis in das Hauptverzeichnis des aktuellen Dateisystems
hinabstieg:

/dev/hda1 on / type ext2
/dev/hda3 on /home type ext2

und ich befinde mich im Verzeichnis /home/wickborn/Studium/usp/pash.

Nun arbeitet er sich korrekt bis ins / vor. Allerdings loest er dabei den
Namen von home nicht richtig auf, da (das konnte ich mit einigen Tests
herausfinden), der inode-Eintrag im "/" auf home ein anderer ist, als in
"/home" der Eintrag "." Ich vermute, das haengt mit dem Mounten zusammen.

Der Test oben wuerde /./wickborn/Studium/usp/pash aufloesen, weil die Inode
von /. die gleiche ist wie die von /home/. Lediglich die st_dev-Eintraege
sind unterschiedlich. Aber dies als Unterscheidungskriterium reicht leider
nicht aus, weil dies auf alle Eintraege des Hauptverzeichnisses zutrifft.

Aber wie kann ich denn nun den Namen korrekt aufloesen? Stevens Buch sagt
dazu leider nichts aus. Wie kann ich mit solchen Mountpunkten umgehen und
die Devicegrenzen ueberwinden? Ist dies ueberhaupt zwischen diversen UN*Xen
portabel moeglich?

Ich danke im Voraus fuer jede Hilfe oder konstruktive Kritik und dafuer,
dass Ihr Euch die Zeit dafuer genommen habt. Sollte ich irgendwelche
relevanten Informationen vergessen haben, lasst es mich wissen.

Beste Gruesse,
--
Fabian Wickborn - wick...@cs.uni-magdeburg.de
http://fabian.wickborn.net
Magdeburger Linux User Group: http://www.mdlug.de - #mdlug on EuIRC

Andi Kübler

unread,
Dec 19, 2001, 10:37:34 AM12/19/01
to
Fabian Wickborn wrote:

> Hallo zusammen!
>
> Im Rahmen meines Studiums soll ich eine Unixfunktion implementieren, welche
> das Workingdirectory des aktuellen Prozesses zusammenstellt - so wie
> getcwd(3), diese schon vorhandene POSIX.1-Funktion soll aber aus
> didaktischen Gruenden nicht verwendet werden.
>
> So weit, so gut. Den Algorithmus habe ich im Stevens ("Adv. Prog. in the
> UNIX Env.") gefunden: Eine Funktion, die im aktuellen Verzeichnis
> (".") beginnt und sich in der Verzeichnishierarchie nach oben arbeitet
> (und dabei ".." benutzt, um ein Ebene hoeher zu kommen). In jedem
> Verzeichnis liest er die Verzeichniseintraege bis er den Namen findet,
> der zu dem I-Node des Verzeichnisses gehoert, aus dem die Funktion
> gerade kommt. Wiederholt man diese Prozedur, bis das Wurzelverzeichnis
> erreicht ist (".." = "."), ergibt sich der gesamte absolute Pfadname
> des aktuellen Arbeitsverzeichnisses.
>

in einer Zeile:


#define workingdir() getenv("PWD")

Gruß,
-Andi

Ulli Horlacher

unread,
Dec 19, 2001, 10:45:13 AM12/19/01
to
Andi Kübler <andi.k...@t-online.de> wrote:

> #define workingdir() getenv("PWD")

Was soll er mit dem NULL-Pointer anfangen?
Diese Implementierung riecht nach Windows :-)


--
-- Ullrich Horlacher, BelWue Coordination ------- mailto:fram...@belwue.de --
Computing Centre University of Stuttgart (RUS) phone: +49 711 685 5868
Allmandring 30, D-70550 Stuttgart, Germany fax: +49 711 678 8363
-- saft://saft.belwue.de/framstag ----------------- http://www.belwue.de/ ----

Fabian Wickborn

unread,
Dec 19, 2001, 10:56:21 AM12/19/01
to
Hallo!

> #define workingdir() getenv("PWD")
>

Dies funktioniert lediglich, wenn PWD durch den laufenden Prozess auch
immer wieder brav gesetzt wird. Koennte ich machen, doch dafuer muesste ich
natuerlich den String erstmal haben. Womit wir wieder bei der Henne und dem
Ei waeren.

Sicherlich waere es moeglich, den String initial aus dem Vaterprozess zu
erhalten und dann implizit zu modifizieren. Allerdings soll ich genau
diesen Weg eben nicht gehen.

Danke dennoch,

Andi Kübler

unread,
Dec 19, 2001, 11:26:46 AM12/19/01
to
Fabian Wickborn wrote:

> Hallo!
>
>
>>#define workingdir() getenv("PWD")
>>
>>
>
> Dies funktioniert lediglich, wenn PWD durch den laufenden Prozess auch
> immer wieder brav gesetzt wird. Koennte ich machen, doch dafuer muesste ich
> natuerlich den String erstmal haben. Womit wir wieder bei der Henne und dem
> Ei waeren.
>
> Sicherlich waere es moeglich, den String initial aus dem Vaterprozess zu
> erhalten und dann implizit zu modifizieren. Allerdings soll ich genau
> diesen Weg eben nicht gehen.


hm, hast du mal mit dem proc-FS gespielt? So in Richtung
readlink("/proc/PID/cwd", buf, sizeof(buf))?

King Leo - Martin Oberzalek

unread,
Dec 19, 2001, 3:22:36 PM12/19/01
to
Fabian Wickborn <wick...@cs.uni-magdeburg.de> writes:


> Der Test oben wuerde /./wickborn/Studium/usp/pash aufloesen, weil die Inode
> von /. die gleiche ist wie die von /home/. Lediglich die st_dev-Eintraege
> sind unterschiedlich. Aber dies als Unterscheidungskriterium reicht leider
> nicht aus, weil dies auf alle Eintraege des Hauptverzeichnisses zutrifft.
>
> Aber wie kann ich denn nun den Namen korrekt aufloesen? Stevens Buch sagt
> dazu leider nichts aus. Wie kann ich mit solchen Mountpunkten umgehen und
> die Devicegrenzen ueberwinden? Ist dies ueberhaupt zwischen diversen UN*Xen
> portabel moeglich?

In C++:

std::string path = //....

while( true )
{
std::string::size_type pos = path.find( "/./" );
if( pos == std::string::npos )
break;

path = path.substr( 0, pos ) + path.substr( pos + 2 );
}


Ja, eigentlch nur Workaround und keine Lösung.

--
2 Pi || ! 2 Pi <- == ?

Gunnar Ritter

unread,
Dec 19, 2001, 5:09:55 PM12/19/01
to
Fabian Wickborn <wick...@cs.uni-magdeburg.de> wrote:

> if ((currentstat.st_dev == parentstat.st_dev) &&
> (currentstat.st_ino == parentstat.st_ino)) {
> return pwdstring;
> }
> else {
> parentdir=opendir("..");
> while ((direntry = readdir(parentdir)) && (
> direntry->d_ino != currentstat.st_ino));

<http://minnie.tuhs.org/TUHS/archive_access.html>
<http://www.tuhs.org/Archive/PDP-11/Trees/V7/usr/src/cmd/pwd.c>
<http://www.sun.com/solaris/source>
<http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/gen/getcwd.c?rev=1.20&content-type=text/x-cvsweb-markup>

Ehe ich Dir aufgrund der Kenntnis dieser Quellen verrate, was
Du falsch machst, kannst Du es auch direkt von dort abschreiben.

Grüße,
Gunnar

Andreas Ferber

unread,
Dec 19, 2001, 5:27:44 PM12/19/01
to
* Fabian Wickborn <wick...@cs.uni-magdeburg.de> schrieb:

>
> Aber wie kann ich denn nun den Namen korrekt aufloesen? Stevens Buch sagt
> dazu leider nichts aus. Wie kann ich mit solchen Mountpunkten umgehen und
> die Devicegrenzen ueberwinden? Ist dies ueberhaupt zwischen diversen UN*Xen
> portabel moeglich?

Beim Traversieren des Parentdirectories den Inode von "subdir/."
vergleichen statt den von "subdir":

[...]
else {
char d[NAME_MAX+6]; /* 6 char for "../"+"/."+NUL */
parentdir = opendir("..");
while ((direntry = readdir(parentdir)) != NULL) {
struct stat s;
/* no length checks because d has been allocated big enough to
* hold the full string in any case */
strcpy(d, "../");
strcat(d, direntry->d_name);
strcat(d, "/.");
if (stat(d, &s) == 0) {
if ((s.st_ino == currentstat.st_ino)
&& (s.st_dev == currentstat.st_dev))
break;


}
}
/* Verz-String um direntry->d_name erweitern */
chdir("..");
}

[...]

Fehlerbehandlung und Check auf "." oder ".." habe ich ausgelassen, um
das Beispiel übersichtlicher zu halten.

Andreas
--
Andreas Ferber - dev/consulting GmbH - Bielefeld, FRG
---------------------------------------------------------
+49 521 1365800 - a...@devcon.net - www.devcon.net

Fabian Wickborn

unread,
Dec 19, 2001, 7:49:33 PM12/19/01
to
Hallo Andreas!

Danke fuer die hilfreiche Loesung. Auf so etwas naheliegendes waere ich
niemals gekommen. Ich dachte schon, ich muesste an die mtab ran oder so
etwas verruecktes.

Frohe Weihnachten und einen guten Rutsch ins neue Jahr,

Gunnar Ritter

unread,
Dec 19, 2001, 9:10:13 PM12/19/01
to
Andreas Ferber <afe...@techfak.uni-bielefeld.de> wrote:

> Beim Traversieren des Parentdirectories den Inode von "subdir/."
> vergleichen statt den von "subdir":

Das ist doch shotgun debugging. Dein Code funktioniert einfach
deshalb, weil Du die i-node-Nummer extra mit stat() ausliest,
anstatt den Wert von readdir() zu verwenden; vgl. den Code
unter den Links, die ich kürzlich gepostet habe. Ob Du einen
Punkt anhängst, ist dabei egal.

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

main()
{
struct dirent *dir;
DIR *dp;
struct stat st1, st2;

chdir("/");
stat("tmp", &st1);
stat("tmp/.", &st2);
dp = opendir(".");
while ((dir = readdir(dp)) != (void *)0)
if (strcmp(dir->d_name, "tmp") == 0)
break;
printf("st1:\tst_dev=%ld\tst_ino=%ld\nst2:\tst_dev=%ld\tst_ino=%ld\n"
"dir:\td_name=%s\td_ino=%ld\n",
(unsigned long)st1.st_dev,
(unsigned long)st1.st_ino,
(unsigned long)st2.st_dev,
(unsigned long)st2.st_ino,
dir->d_name,
(unsigned long)dir->d_ino);
}

ergibt hier

st1: st_dev=9 st_ino=296
st2: st_dev=9 st_ino=296
dir: d_name=tmp d_ino=141358

Und wenn sich st1 und st2 tatsächlich bei irgendwem unterscheiden
sollten, wäre das ein schlichter Bug.

Grüße,
Gunnar

Andreas Ferber

unread,
Dec 20, 2001, 12:13:58 AM12/20/01
to
* Gunnar Ritter <g...@bigfoot.de> schrieb:

>
> Das ist doch shotgun debugging. Dein Code funktioniert einfach
> deshalb, weil Du die i-node-Nummer extra mit stat() ausliest,
> anstatt den Wert von readdir() zu verwenden; vgl. den Code
> unter den Links, die ich kürzlich gepostet habe. Ob Du einen
> Punkt anhängst, ist dabei egal.

Ack, fiel mir nach dem Posten auch auf. Allerdings betrachte ich das
nicht als so gravierend, daß ich deswegen einen Supersede geschickt
hätte.

Der Vollständigkeit halber hier nochmal der "bereinigte" Code:

[...]
else {
chdir("..");
parentdir = opendir(".");


while ((direntry = readdir(parentdir)) != NULL) {
struct stat s;

if (stat(direntry->d_name, &s) == 0) {


if ((s.st_ino == currentstat.st_ino)
&& (s.st_dev == currentstat.st_dev))
break;
}
}
/* Verz-String um direntry->d_name erweitern */
}

[...]

Ausserdem sei noch erwähnt, daß der Code Race-Conditions enthält, die
nicht trivial vermeidbar sind, daher ist eine ordentliche
Fehlerbehandlung essentiell wichtig, wenn er tatsächlich benutzt werden
soll.

Felix von Leitner

unread,
Dec 20, 2001, 11:25:25 PM12/20/01
to
Thus spake Gunnar Ritter (g...@bigfoot.de):

> Das ist doch shotgun debugging. Dein Code funktioniert einfach
> deshalb, weil Du die i-node-Nummer extra mit stat() ausliest,
> anstatt den Wert von readdir() zu verwenden;

Bitte?!
Seit wann gibt dir readdir() die inode?
Du bist doch hier sonst eher der Standard Lawyer...?!

> vgl. den Code unter den Links, die ich kürzlich gepostet habe. Ob Du
> einen Punkt anhängst, ist dabei egal.

Das hätte ich auch gesagt. Was mir bei euch beiden aber noch fehlt ist
Symlink Handling. Wenn readdir zufällig vor dem tatsächlichen
Verzeichnis einen Symlink auf das Verzeichnis liefert, gibt's sonst
einen unerwarteten Pfad.

> st1: st_dev=9 st_ino=296
> st2: st_dev=9 st_ino=296
> dir: d_name=tmp d_ino=141358

> Und wenn sich st1 und st2 tatsächlich bei irgendwem unterscheiden
> sollten, wäre das ein schlichter Bug.

Bei NFS ist das nicht so klar IIRC. Da ist es im Grunde eine Race
Condition, weil auf manchen Plattformen alle NFS Volumes die device ID
"-1" haben und die Inode halt aus dem NFS Handle gehashed wird oder
vielleicht ein Index in einer Tabelle ist, die periodisch garbage
collected wird o.ä.

Also so richtig sauber funktioniert obiger Algorithmus nicht. Daher
gibt es bei anständigen Systemen ja auch ein getcwd als Syscall oder
/proc-Datei.

Felix

Frank Klemm

unread,
Dec 21, 2001, 9:46:30 AM12/21/01
to
On Fri, 21 Dec 2001 04:25:25 GMT, Felix von Leitner <usenet-...@fefe.de> wrote:
>
>Bei NFS ist das nicht so klar IIRC. Da ist es im Grunde eine Race
>Condition, weil auf manchen Plattformen alle NFS Volumes die device ID
>"-1" haben und die Inode halt aus dem NFS Handle gehashed wird oder
>vielleicht ein Index in einer Tabelle ist, die periodisch garbage
>collected wird o.ä.
>
>Also so richtig sauber funktioniert obiger Algorithmus nicht. Daher
>gibt es bei anständigen Systemen ja auch ein getcwd als Syscall oder
>/proc-Datei.
>
Ich hatte mal mächtigen Ärger mit dem "Suche mich selbst"-Algorithmus,
um das aktuelle Verzeichnis in die Textrepräsentation zu bringen.

Da das Mutterverzeichnis einige zigtausend Dateien enthielt, dauerte
dieses Bestimmen einige Minuten. Und es wurde nach jedem auf dem
Prompt ausgeführten Kommando ausgeführt, um das aktuelle Kommandoprompt zu
bestimmen.

Seitdem weiß ich von den Schwächen dieses Vorgehens.

--
Frank

Gunnar Ritter

unread,
Dec 21, 2001, 7:48:18 PM12/21/01
to
Felix von Leitner <usenet-...@fefe.de> wrote:

> Seit wann gibt dir readdir() die inode?

Seit jeher und so gut wie überall, weil die i-node-Nummer
aus naheliegenden Gründen traditionell Bestandteil der struct
direct war und readdir() das mit der struct dirent nachbildet.

> Du bist doch hier sonst eher der Standard Lawyer...?!

Vgl. <3A832A8B...@bigfoot.de>

> Was mir bei euch beiden aber noch fehlt ist Symlink Handling.

Euch beiden? Ich habe keinen Code für getcwd() gepostet, bei v7 ist
das geschenkt, die Solaris-Implementation verwendet ebenso lstat()
wie die von FreeBSD.

> Bei NFS ist das nicht so klar IIRC. Da ist es im Grunde eine Race
> Condition, weil auf manchen Plattformen alle NFS Volumes die device ID
> "-1" haben und die Inode halt aus dem NFS Handle gehashed wird oder
> vielleicht ein Index in einer Tabelle ist, die periodisch garbage
> collected wird o.ä.

Das wäre immerhin ein Verstoß gegen POSIX.1, wonach st_dev
und st_ino »meaningful values« haben müssen, eine Device ID
non-negative sein muß und eine File Serial Number ein »per-file
system unique identifier for a file« zu sein hat (Belegstellen
im neuen Jahr, falls gewünscht).

Grüße,
Gunnar

Felix von Leitner

unread,
Dec 21, 2001, 9:17:06 PM12/21/01
to
Thus spake Gunnar Ritter (g...@bigfoot.de):
> > Seit wann gibt dir readdir() die inode?
> Seit jeher und so gut wie überall, weil die i-node-Nummer
> aus naheliegenden Gründen traditionell Bestandteil der struct
> direct war und readdir() das mit der struct dirent nachbildet.

Mhh, hab ich wieder zu schnell meiner man page getraut :-(

> > Was mir bei euch beiden aber noch fehlt ist Symlink Handling.
> Euch beiden? Ich habe keinen Code für getcwd() gepostet, bei v7 ist
> das geschenkt, die Solaris-Implementation verwendet ebenso lstat()
> wie die von FreeBSD.

Mhh. Ich hab nachts offenbar nen Knick in der Optik. Sorry.

> Das wäre immerhin ein Verstoß gegen POSIX.1,

SunOS 4 hatte eh noch nicht so den POSIX Vorbildcharakter.

Felix

0 new messages