1 /*
2 cd_to() function.
3
4 Copyright (C) 1995-2025
5 Free Software Foundation, Inc.
6
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2020
10
11 This file is part of the Midnight Commander.
12
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
17
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
27 /** \file cd.c
28 * \brief Source: cd_to() function
29 */
30
31 #include <config.h>
32
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "lib/global.h"
37 #include "lib/vfs/vfs.h"
38 #include "lib/strutil.h"
39 #include "lib/util.h" // whitespace()
40
41 #include "src/util.h" // file_error_message()
42
43 #include "filemanager.h" // current_panel, panel.h, layout.h
44 #include "tree.h" // sync_tree()
45
46 #include "cd.h"
47
48 /*** global variables ****************************************************************************/
49
50 /*** file scope macro definitions ****************************************************************/
51
52 /*** file scope type declarations ****************************************************************/
53
54 /*** forward declarations (file scope functions) *************************************************/
55
56 /*** file scope variables ************************************************************************/
57
58 /* --------------------------------------------------------------------------------------------- */
59 /*** file scope functions ************************************************************************/
60 /* --------------------------------------------------------------------------------------------- */
61
62 /**
63 * Expand the argument to "cd" and change directory. First try tilde
64 * expansion, then variable substitution. If the CDPATH variable is set
65 * (e.g. CDPATH=".:~:/usr"), try all the paths contained there.
66 * We do not support such rare substitutions as ${var:-value} etc.
67 * No quoting is implemented here, so ${VAR} and $VAR will be always
68 * substituted. Wildcards are not supported either.
69 * Advanced users should be encouraged to use "\cd" instead of "cd" if
70 * they want the behavior they are used to in the shell.
71 *
72 * @param _path string to examine
73 * @return newly allocated string
74 */
75
76 static GString *
77 examine_cd (const char *_path)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
78 {
79 typedef enum
80 {
81 copy_sym,
82 subst_var
83 } state_t;
84
85 state_t state = copy_sym;
86 GString *q;
87 char *path_tilde, *path;
88 char *p;
89
90 // Tilde expansion
91 path = str_shell_unescape (_path);
92 path_tilde = tilde_expand (path);
93 g_free (path);
94
95 q = g_string_sized_new (32);
96
97 // Variable expansion
98 for (p = path_tilde; *p != '\0';)
99 {
100 switch (state)
101 {
102 case copy_sym:
103 if (p[0] == '\\' && p[1] == '$')
104 {
105 g_string_append_c (q, '$');
106 p += 2;
107 }
108 else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
109 {
110 g_string_append_c (q, *p);
111 p++;
112 }
113 else
114 state = subst_var;
115 break;
116
117 case subst_var:
118 {
119 char *s = NULL;
120 char c;
121 const char *t = NULL;
122
123 // skip dollar
124 p++;
125
126 if (p[0] == '{')
127 {
128 p++;
129 s = strchr (p, '}');
130 }
131 if (s == NULL)
132 s = strchr (p, PATH_SEP);
133 if (s == NULL)
134 s = strchr (p, '\0');
135 c = *s;
136 *s = '\0';
137 t = getenv (p);
138 *s = c;
139 if (t == NULL)
140 {
141 g_string_append_c (q, '$');
142 if (p[-1] != '$')
143 g_string_append_c (q, '{');
144 }
145 else
146 {
147 g_string_append (q, t);
148 p = s;
149 if (*s == '}')
150 p++;
151 }
152
153 state = copy_sym;
154 break;
155 }
156
157 default:
158 break;
159 }
160 }
161
162 g_free (path_tilde);
163
164 return q;
165 }
166
167 /* --------------------------------------------------------------------------------------------- */
168
169 /* CDPATH handling */
170 static gboolean
171 handle_cdpath (const char *path)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
172 {
173 gboolean result = FALSE;
174
175 // CDPATH handling
176 if (!IS_PATH_SEP (*path))
177 {
178 char *cdpath, *p;
179 char c;
180
181 cdpath = g_strdup (getenv ("CDPATH"));
182 p = cdpath;
183 c = (p == NULL) ? '\0' : ':';
184
185 while (!result && c == ':')
186 {
187 char *s;
188
189 s = strchr (p, ':');
190 if (s == NULL)
191 s = strchr (p, '\0');
192 c = *s;
193 *s = '\0';
194 if (*p != '\0')
195 {
196 vfs_path_t *r_vpath;
197
198 r_vpath = vfs_path_build_filename (p, path, (char *) NULL);
199 result = panel_cd (current_panel, r_vpath, cd_parse_command);
200 vfs_path_free (r_vpath, TRUE);
201 }
202 *s = c;
203 p = s + 1;
204 }
205 g_free (cdpath);
206 }
207
208 return result;
209 }
210
211 /* --------------------------------------------------------------------------------------------- */
212 /*** public functions ****************************************************************************/
213 /* --------------------------------------------------------------------------------------------- */
214
215 /** Execute the cd command to specified path
216 *
217 * @param path path to cd
218 */
219
220 void
221 cd_to (const char *path)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
222 {
223 char *p;
224
225 // Remove leading whitespaces.
226 // Any final whitespace should be removed here (to see why, try "cd fred ").
227 /* NOTE: I think we should not remove the extra space,
228 that way, we can cd into hidden directories */
229 /* FIXME: what about interpreting quoted strings like the shell.
230 so one could type "cd <tab> M-a <enter>" and it would work. */
231 p = g_strstrip (g_strdup (path));
232
233 if (get_current_type () == view_tree)
234 {
235 vfs_path_t *new_vpath = NULL;
236
237 if (p[0] == '\0')
238 {
239 new_vpath = vfs_path_from_str (mc_config_get_home_dir ());
240 sync_tree (new_vpath);
241 }
242 else if (DIR_IS_DOTDOT (p))
243 {
244 if (vfs_path_elements_count (current_panel->cwd_vpath) != 1
245 || strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
246 {
247 vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
248
249 current_panel->cwd_vpath =
250 vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
251 vfs_path_free (tmp_vpath, TRUE);
252 }
253 sync_tree (current_panel->cwd_vpath);
254 }
255 else
256 {
257 if (IS_PATH_SEP (*p))
258 new_vpath = vfs_path_from_str (p);
259 else
260 new_vpath = vfs_path_append_new (current_panel->cwd_vpath, p, (char *) NULL);
261
262 sync_tree (new_vpath);
263 }
264
265 vfs_path_free (new_vpath, TRUE);
266 }
267 else
268 {
269 GString *s_path;
270 vfs_path_t *q_vpath;
271 gboolean ok;
272
273 s_path = examine_cd (p);
274
275 if (s_path->len == 0)
276 q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
277 else
278 q_vpath = vfs_path_from_str_flags (s_path->str, VPF_NO_CANON);
279
280 ok = panel_cd (current_panel, q_vpath, cd_parse_command);
281 if (!ok)
282 ok = handle_cdpath (s_path->str);
283 if (!ok)
284 {
285 char *d;
286
287 d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
288 cd_error_message (d);
289 g_free (d);
290 }
291
292 vfs_path_free (q_vpath, TRUE);
293 g_string_free (s_path, TRUE);
294 }
295
296 g_free (p);
297 }
298
299 /* --------------------------------------------------------------------------------------------- */
300
301 void
302 cd_error_message (const char *path)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
303 {
304 file_error_message (_ ("Cannot change directory to\n%s"), path);
305 }
306
307 /* --------------------------------------------------------------------------------------------- */