~emersion/mrsh

ref: 448ee4f664f60d70d229af5462ef349240af8d7f mrsh/builtin/cd.c -rw-r--r-- 3.4 KiB
448ee4f6 — Ben Brown Reset optind to 0 1 year, 10 months ago
                                                                                
23791269 delthas
448ee4f6 Ben Brown
23791269 delthas
23791269 delthas
23791269 delthas
23791269 delthas
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
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <limits.h>
#include <mrsh/shell.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "builtin.h"

static const char cd_usage[] = "usage: cd [-L|-P] [-|directory]\n";

static int cd(struct mrsh_state *state, const char *path) {
	const char *oldPWD = mrsh_env_get(state, "PWD", NULL);
	if (chdir(path) != 0) {
		// TODO make better error messages
		fprintf(stderr, "cd: %s\n", strerror(errno));
		return EXIT_FAILURE;
	}
	char cwd[PATH_MAX];
	if (getcwd(cwd, PATH_MAX) == NULL) {
		fprintf(stderr, "cd: Cannot set new PWD as the path "
			"is too long\n");
		return EXIT_FAILURE;
	}
	mrsh_env_set(state, "OLDPWD", oldPWD, MRSH_VAR_ATTRIB_NONE);
	mrsh_env_set(state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT);
	return EXIT_SUCCESS;
}

static int isdir(char *path) {
	struct stat s;
	stat(path, &s);
	return S_ISDIR(s.st_mode);
}

int builtin_cd(struct mrsh_state *state, int argc, char *argv[]) {
	optind = 0;
	int opt;
	while ((opt = getopt(argc, argv, ":LP")) != -1) {
		switch (opt) {
		case 'L':
		case 'P':
			// TODO implement `-L` and `-P`
			fprintf(stderr, "cd: `-L` and `-P` not yet implemented\n");
			return EXIT_FAILURE;
		default:
			fprintf(stderr, "cd: unknown option -- %c\n", optopt);
			fprintf(stderr, cd_usage);
			return EXIT_FAILURE;
		}
	}
	if (optind + 1 > argc) {
		fprintf(stderr, cd_usage);
		return EXIT_FAILURE;
	}

	if (optind == argc) {
		const char *home = mrsh_env_get(state, "HOME", NULL);
		if (home && home[0] != '\0') {
			return cd(state, home);
		}
		fprintf(stderr, "cd: No arguments were given and $HOME "
			"is not defined.\n");
		return EXIT_FAILURE;
	}

	char *curpath = argv[optind];
	// `cd -`
	if (strcmp(curpath, "-") == 0) {
		// This case is special as we print `pwd` at the end
		const char *oldpwd = mrsh_env_get(state, "OLDPWD", NULL);
		const char *pwd = mrsh_env_get(state, "PWD", NULL);
		if (!pwd) {
			fprintf(stderr, "cd: PWD is not set\n");
			return EXIT_FAILURE;
		}
		if (!oldpwd) {
			fprintf(stderr, "cd: OLDPWD is not set\n");
			return EXIT_FAILURE;
		}
		if (chdir(oldpwd) != 0) {
			fprintf(stderr, "cd: %s\n", strerror(errno));
			return EXIT_FAILURE;
		}
		char *_pwd = strdup(pwd);
		puts(oldpwd);
		mrsh_env_set(state, "PWD", oldpwd, MRSH_VAR_ATTRIB_EXPORT);
		mrsh_env_set(state, "OLDPWD", _pwd, MRSH_VAR_ATTRIB_NONE);
		free(_pwd);
		return EXIT_SUCCESS;
	}
	// $CDPATH
	if (curpath[0] != '/' && strncmp(curpath, "./", 2) != 0 &&
			strncmp(curpath, "../", 3) != 0) {
		const char *_cdpath = mrsh_env_get(state, "CDPATH", NULL);
		char *cdpath = NULL;
		if (_cdpath) {
			cdpath = strdup(_cdpath);
		}
		char *c = cdpath;
		while (c != NULL) {
			char *next = strchr(c, ':');
			char *slash = strrchr(c, '/');
			if (next) {
				*next = '\0';
				++next;
			}
			if (*c == '\0') {
				// path is empty
				c = ".";
				slash = NULL;
			}
			int len;
			char path[PATH_MAX];
			if (slash == NULL || slash[1] != '\0') {
				// the last character is not a slash
				len = snprintf(path, PATH_MAX, "%s/%s", c,
					curpath);
			} else {
				len = snprintf(path, PATH_MAX, "%s%s", c,
					curpath);
			}
			if (len >= PATH_MAX) {
				fprintf(stderr, "cd: Cannot search $CDPATH "
					"directory \"%s\" since it exceeds the "
					"maximum path length\n", c);
				continue;
			}
			if (isdir(path)) {
				free(cdpath);
				return cd(state, path);
			}
			c = next;
		}
		free(cdpath);
	}
	return cd(state, curpath);
}