#pragma prototyped
#include <ast.h>
#if _WINIX
NoN(getcwd)
#else
#include <ast_dir.h>
#include <error.h>
#include <fs3d.h>
#ifndef ERANGE
#define ERANGE E2BIG
#endif
#define ERROR(e) { errno = e; goto error; }
struct dirlist
{
struct dirlist* next;
int index;
};
static int
popdir(register struct dirlist* d, register char* end)
{
register struct dirlist* dp;
int v;
v = 0;
while (dp = d)
{
d = d->next;
if (!v)
{
if (d) *(end - d->index - 1) = 0;
v = chdir(end - dp->index);
if (d) *(end - d->index - 1) = '/';
}
free(dp);
}
return v;
}
static struct dirlist*
pushdir(register struct dirlist* d, char* dots, char* path, char* end)
{
register struct dirlist* p;
if (!(p = newof(0, struct dirlist, 1, 0)) || chdir(dots))
{
if (p) free(p);
if (d) popdir(d, end);
return 0;
}
p->index = end - path;
p->next = d;
return p;
}
char*
getcwd(char* buf, size_t len)
{
register char* d;
register char* p;
register char* s;
DIR* dirp = 0;
int n;
int x;
size_t namlen;
ssize_t extra = -1;
struct dirent* entry;
struct dirlist* dirstk = 0;
struct stat* cur;
struct stat* par;
struct stat* tmp;
struct stat curst;
struct stat parst;
struct stat tstst;
char dots[PATH_MAX];
static struct
{
char* name;
char* path;
dev_t dev;
ino_t ino;
} env[] =
{
{ 0 },
{ "PWD" },
{ "HOME" },
};
if (buf && !len) ERROR(EINVAL);
if (fs3d(FS3D_TEST) && (namlen = mount(".", dots, FS3D_GET|FS3D_VIEW|FS3D_SIZE(sizeof(dots)), NiL)) > 1 && namlen < sizeof(dots))
{
p = dots;
easy:
namlen++;
if (buf)
{
if (len < namlen) ERROR(ERANGE);
}
else if (!(buf = newof(0, char, namlen, len))) ERROR(ENOMEM);
return (char*)memcpy(buf, p, namlen);
}
cur = &curst;
par = &parst;
if (stat(".", par)) ERROR(errno);
for (n = 0; n < elementsof(env); n++)
{
if ((env[n].name && (p = getenv(env[n].name)) || (p = env[n].path)) && *p == '/' && !stat(p, cur))
{
env[n].path = p;
env[n].dev = cur->st_dev;
env[n].ino = cur->st_ino;
if (cur->st_ino == par->st_ino && cur->st_dev == par->st_dev)
{
namlen = strlen(p);
goto easy;
}
}
}
if (!buf)
{
extra = len;
len = PATH_MAX;
if (!(buf = newof(0, char, len, extra))) ERROR(ENOMEM);
}
d = dots;
p = buf + len - 1;
*p = 0;
n = elementsof(env);
for (;;)
{
tmp = cur;
cur = par;
par = tmp;
if ((d - dots) > (PATH_MAX - 4))
{
if (!(dirstk = pushdir(dirstk, dots, p, buf + len - 1))) ERROR(ERANGE);
d = dots;
}
*d++ = '.';
*d++ = '.';
*d = 0;
if (!(dirp = opendir(dots))) ERROR(errno);
#if !_dir_ok || _mem_dd_fd_DIR
if (fstat(dirp->dd_fd, par)) ERROR(errno);
#else
if (stat(dots, par)) ERROR(errno);
#endif
*d++ = '/';
if (par->st_dev == cur->st_dev)
{
if (par->st_ino == cur->st_ino)
{
closedir(dirp);
*--p = '/';
pop:
if (p != buf)
{
d = buf;
while (*d++ = *p++);
len = d - buf;
if (extra >= 0 && !(buf = newof(buf, char, len, extra))) ERROR(ENOMEM);
}
if (dirstk && popdir(dirstk, buf + len - 1))
{
dirstk = 0;
ERROR(errno);
}
if (env[0].path)
free(env[0].path);
env[0].path = strdup(buf);
return buf;
}
#ifdef D_FILENO
while (entry = readdir(dirp))
if (D_FILENO(entry) == cur->st_ino)
{
namlen = D_NAMLEN(entry);
goto found;
}
#endif
rewinddir(dirp);
}
do
{
if (!(entry = readdir(dirp))) ERROR(ENOENT);
namlen = D_NAMLEN(entry);
if ((d - dots) > (PATH_MAX - 1 - namlen))
{
*d = 0;
if (namlen >= PATH_MAX || !(dirstk = pushdir(dirstk, dots + 3, p, buf + len - 1))) ERROR(ERANGE);
d = dots + 3;
}
memcpy(d, entry->d_name, namlen + 1);
} while (stat(dots, &tstst) || tstst.st_ino != cur->st_ino || tstst.st_dev != cur->st_dev);
found:
if (*p) *--p = '/';
while ((p -= namlen) <= (buf + 1))
{
x = (buf + len - 1) - (p += namlen);
s = buf + len;
if (extra < 0 || !(buf = newof(buf, char, len += PATH_MAX, extra))) ERROR(ERANGE);
p = buf + len;
while (p > buf + len - 1 - x) *--p = *--s;
}
if (n < elementsof(env))
{
memcpy(p, env[n].path, namlen);
goto pop;
}
memcpy(p, entry->d_name, namlen);
closedir(dirp);
dirp = 0;
for (n = 0; n < elementsof(env); n++)
if (env[n].ino == par->st_ino && env[n].dev == par->st_dev)
{
namlen = strlen(env[n].path);
goto found;
}
}
error:
if (buf)
{
if (dirstk) popdir(dirstk, buf + len - 1);
if (extra >= 0) free(buf);
}
if (dirp) closedir(dirp);
return 0;
}
#endif