aboutsummaryrefslogtreecommitdiffstats
path: root/core/fs/chdir.c
blob: c40e91bfbf355238f83dd167a90bc780d94412fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <dprintf.h>
#include "fs.h"
#include "cache.h"

/*
 * Convert a relative pathname to an absolute pathname
 * In the future this might also resolve symlinks...
 */
void pm_realpath(com32sys_t *regs)
{
    const char *src = MK_PTR(regs->ds, regs->esi.w[0]);
    char       *dst = MK_PTR(regs->es, regs->edi.w[0]);

    realpath(dst, src, FILENAME_MAX);
}

#define EMIT(x)				\
do {					\
    if (++n < bufsize)			\
	*q++ = (x);			\
} while (0)


static size_t join_paths(char *dst, size_t bufsize,
			 const char *s1, const char *s2)
{
    const char *list[2];
    int i;
    char c;
    const char *p;
    char *q  = dst;
    size_t n = 0;
    bool slash;
    struct bufstat {
	struct bufstat *prev;
	size_t n;
	char *q;
    };
    struct bufstat *stk = NULL;
    struct bufstat *bp;
    
    slash = false;

    list[0] = s1;
    list[1] = s2;

    for (i = 0; i < 2; i++) {
	p = list[i];

	while ((c = *p++)) {
	    if (c == '/') {
		if (!slash) {
		    EMIT(c);
		    bp = malloc(sizeof *bp);
		    bp->n = n;
		    bp->q = q;
		    bp->prev = stk;
		    stk = bp;
		}
		slash = true;
	    } else if (c == '.' && slash) {
		if (!*p || *p == '/') {
		    continue;	/* Single dot */
		}
		if (*p == '.' && (!p[1] || p[1] == '/')) {
		    /* Double dot; unwind one level */
		    p++;
		    if (stk) {
			bp = stk;
			stk = stk->prev;
			free(bp);
		    }
		    if (stk) {
			n = stk->n;
			q = stk->q;
		    }
		    continue;
		}
	    } else {
		EMIT(c);
		slash = false;
	    }
	}
    }

    if (bufsize)
	*q = '\0';

    while ((bp = stk)) {
	stk = stk->prev;
	free(bp);
    }

    return n;
}

size_t realpath(char *dst, const char *src, size_t bufsize)
{
    if (this_fs->fs_ops->realpath) {
	return this_fs->fs_ops->realpath(this_fs, dst, src, bufsize);
    } else {
	/* Filesystems with "common" pathname resolution */
	return join_paths(dst, bufsize, 
			  src[0] == '/' ? "" : this_fs->cwd_name,
			  src);
    }
}

int chdir(const char *src)
{
    int rv;
    struct file *file;
    char cwd_buf[CURRENTDIR_MAX];

    dprintf("chdir: from %s add %s\n", this_fs->cwd_name, src);

    if (this_fs->fs_ops->chdir)
	return this_fs->fs_ops->chdir(this_fs, src);

    /* Otherwise it is a "conventional filesystem" */
    rv = searchdir(src);
    if (rv < 0)
	return rv;

    file = handle_to_file(rv);
    if (file->inode->mode != DT_DIR) {
	_close_file(file);
	return -1;
    }

    put_inode(this_fs->cwd);
    this_fs->cwd = get_inode(file->inode);
    _close_file(file);

    /* Save the current working directory */
    realpath(cwd_buf, src, CURRENTDIR_MAX);

    /* Make sure the cwd_name ends in a slash, it's supposed to be a prefix */
    join_paths(this_fs->cwd_name, CURRENTDIR_MAX, cwd_buf, "/");

    dprintf("chdir: final: %s\n", this_fs->cwd_name);

    return 0;
}