Merge branch '4535_tmpdir_name'
[midnight-commander.git] / src / filemanager / cd.c
blob017d3ea815f7f860bd24727439baeddd1308b9fb
1 /*
2 cd_to() function.
4 Copyright (C) 1995-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2020
11 This file is part of the Midnight Commander.
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.
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.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /** \file cd.c
28 * \brief Source: cd_to() function
31 #include <config.h>
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <string.h>
37 #include "lib/global.h"
38 #include "lib/vfs/vfs.h"
39 #include "lib/strutil.h"
40 #include "lib/util.h" /* whitespace() */
41 #include "lib/widget.h" /* message() */
43 #include "filemanager.h" /* current_panel, panel.h, layout.h */
44 #include "tree.h" /* sync_tree() */
46 #include "cd.h"
48 /*** global variables ****************************************************************************/
50 /*** file scope macro definitions ****************************************************************/
52 /*** file scope type declarations ****************************************************************/
54 /*** forward declarations (file scope functions) *************************************************/
56 /*** file scope variables ************************************************************************/
58 /* --------------------------------------------------------------------------------------------- */
59 /*** file scope functions ************************************************************************/
60 /* --------------------------------------------------------------------------------------------- */
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.
72 * @param _path string to examine
73 * @return newly allocated string
76 static GString *
77 examine_cd (const char *_path)
79 /* *INDENT-OFF* */
80 typedef enum
82 copy_sym,
83 subst_var
84 } state_t;
85 /* *INDENT-ON* */
87 state_t state = copy_sym;
88 GString *q;
89 char *path_tilde, *path;
90 char *p;
92 /* Tilde expansion */
93 path = str_shell_unescape (_path);
94 path_tilde = tilde_expand (path);
95 g_free (path);
97 q = g_string_sized_new (32);
99 /* Variable expansion */
100 for (p = path_tilde; *p != '\0';)
102 switch (state)
104 case copy_sym:
105 if (p[0] == '\\' && p[1] == '$')
107 g_string_append_c (q, '$');
108 p += 2;
110 else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
112 g_string_append_c (q, *p);
113 p++;
115 else
116 state = subst_var;
117 break;
119 case subst_var:
121 char *s = NULL;
122 char c;
123 const char *t = NULL;
125 /* skip dollar */
126 p++;
128 if (p[0] == '{')
130 p++;
131 s = strchr (p, '}');
133 if (s == NULL)
134 s = strchr (p, PATH_SEP);
135 if (s == NULL)
136 s = strchr (p, '\0');
137 c = *s;
138 *s = '\0';
139 t = getenv (p);
140 *s = c;
141 if (t == NULL)
143 g_string_append_c (q, '$');
144 if (p[-1] != '$')
145 g_string_append_c (q, '{');
147 else
149 g_string_append (q, t);
150 p = s;
151 if (*s == '}')
152 p++;
155 state = copy_sym;
156 break;
159 default:
160 break;
164 g_free (path_tilde);
166 return q;
169 /* --------------------------------------------------------------------------------------------- */
171 /* CDPATH handling */
172 static gboolean
173 handle_cdpath (const char *path)
175 gboolean result = FALSE;
177 /* CDPATH handling */
178 if (!IS_PATH_SEP (*path))
180 char *cdpath, *p;
181 char c;
183 cdpath = g_strdup (getenv ("CDPATH"));
184 p = cdpath;
185 c = (p == NULL) ? '\0' : ':';
187 while (!result && c == ':')
189 char *s;
191 s = strchr (p, ':');
192 if (s == NULL)
193 s = strchr (p, '\0');
194 c = *s;
195 *s = '\0';
196 if (*p != '\0')
198 vfs_path_t *r_vpath;
200 r_vpath = vfs_path_build_filename (p, path, (char *) NULL);
201 result = panel_cd (current_panel, r_vpath, cd_parse_command);
202 vfs_path_free (r_vpath, TRUE);
204 *s = c;
205 p = s + 1;
207 g_free (cdpath);
210 return result;
213 /* --------------------------------------------------------------------------------------------- */
214 /*** public functions ****************************************************************************/
215 /* --------------------------------------------------------------------------------------------- */
217 /** Execute the cd command to specified path
219 * @param path path to cd
222 void
223 cd_to (const char *path)
225 char *p;
227 /* Remove leading whitespaces. */
228 /* Any final whitespace should be removed here (to see why, try "cd fred "). */
229 /* NOTE: I think we should not remove the extra space,
230 that way, we can cd into hidden directories */
231 /* FIXME: what about interpreting quoted strings like the shell.
232 so one could type "cd <tab> M-a <enter>" and it would work. */
233 p = g_strstrip (g_strdup (path));
235 if (get_current_type () == view_tree)
237 vfs_path_t *new_vpath = NULL;
239 if (p[0] == '\0')
241 new_vpath = vfs_path_from_str (mc_config_get_home_dir ());
242 sync_tree (new_vpath);
244 else if (DIR_IS_DOTDOT (p))
246 if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
247 strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
249 vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
251 current_panel->cwd_vpath =
252 vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
253 vfs_path_free (tmp_vpath, TRUE);
255 sync_tree (current_panel->cwd_vpath);
257 else
259 if (IS_PATH_SEP (*p))
260 new_vpath = vfs_path_from_str (p);
261 else
262 new_vpath = vfs_path_append_new (current_panel->cwd_vpath, p, (char *) NULL);
264 sync_tree (new_vpath);
267 vfs_path_free (new_vpath, TRUE);
269 else
271 GString *s_path;
272 vfs_path_t *q_vpath;
273 gboolean ok;
275 s_path = examine_cd (p);
277 if (s_path->len == 0)
278 q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
279 else
280 q_vpath = vfs_path_from_str_flags (s_path->str, VPF_NO_CANON);
282 ok = panel_cd (current_panel, q_vpath, cd_parse_command);
283 if (!ok)
284 ok = handle_cdpath (s_path->str);
285 if (!ok)
287 char *d;
289 d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
290 cd_error_message (d);
291 g_free (d);
294 vfs_path_free (q_vpath, TRUE);
295 g_string_free (s_path, TRUE);
298 g_free (p);
301 /* --------------------------------------------------------------------------------------------- */
303 void
304 cd_error_message (const char *path)
306 message (D_ERROR, MSG_ERROR, _("Cannot change directory to\n%s\n%s"), path,
307 unix_error_string (errno));
310 /* --------------------------------------------------------------------------------------------- */