Skip to content

Commit 6599694

Browse files
committed
fix: strip trailing slash from argument before path operations (closes #450)
A trailing slash on a symlink caused rename() to fail with ENOTDIR because the kernel dereferenced the symlink. Strip any trailing slash from the argument early, before check_pathname_state, lstat, resolve_path, and rename, so "foo/" is treated identically to "foo". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Fixes #450
1 parent 5b36817 commit 6599694

3 files changed

Lines changed: 26 additions & 19 deletions

File tree

src/main.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ remove_to_waste(const int argc,
289289
char tmp[PATH_MAX];
290290
sn_check(snprintf(tmp, sizeof(tmp), "%s", argv[file_arg]), sizeof(tmp));
291291

292+
trim_char('/', tmp);
293+
294+
/* keep a copy before basename() may clobber tmp */
295+
char arg[PATH_MAX];
296+
sn_check(snprintf(arg, sizeof(arg), "%s", tmp), sizeof(arg));
297+
292298
// If basename() is given an empty string, it returns '.'
293299
st_target.base_name = basename(tmp);
294300
if (isdotdir(st_target.base_name))
@@ -309,7 +315,7 @@ damage of 5000 hp. You feel satisfied.\n"));
309315
continue;
310316
}
311317

312-
int p_state = check_pathname_state(argv[file_arg]);
318+
int p_state = check_pathname_state(arg);
313319
if (p_state != EEXIST)
314320
{
315321
if (p_state == ENOENT)
@@ -319,10 +325,10 @@ damage of 5000 hp. You feel satisfied.\n"));
319325
}
320326

321327
struct stat st_file_arg;
322-
if (!lstat(argv[file_arg], &st_file_arg))
328+
if (!lstat(arg, &st_file_arg))
323329
{
324330
st_target.dev_num = st_file_arg.st_dev;
325-
st_target.real_path = resolve_path(argv[file_arg], st_target.base_name);
331+
st_target.real_path = resolve_path(arg, st_target.base_name);
326332
if (st_target.real_path == NULL)
327333
{
328334
n_err++;
@@ -411,7 +417,7 @@ damage of 5000 hp. You feel satisfied.\n"));
411417
int r_result = 0;
412418
if (cli_user_options->want_dry_run == false)
413419
{
414-
const char *src = argv[file_arg];
420+
const char *src = arg;
415421
const char *dst = st_target.waste_dest_name;
416422

417423
if (waste_curr->dev_num != st_target.dev_num)

src/utils.c

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -261,25 +261,23 @@ resolve_path(const char *file, const char *b)
261261
void
262262
trim_char(const int c, char *str)
263263
{
264-
char *dup_str = str;
265-
trim_whitespace(dup_str);
266-
if (*dup_str == '\0')
264+
char *p = str;
265+
trim_whitespace(p);
266+
if (*p == '\0')
267267
return;
268268

269-
while (*dup_str != '\0')
270-
dup_str++;
269+
while (*p != '\0')
270+
p++;
271271

272-
dup_str--;
272+
p--;
273273

274-
while (*dup_str == c)
274+
while (*p == c)
275275
{
276-
*dup_str = '\0';
277-
if (dup_str == str)
276+
*p = '\0';
277+
if (p == str)
278278
return;
279-
dup_str--;
279+
p--;
280280
}
281-
282-
return;
283281
}
284282

285283

test/test_restore.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,15 @@ if [ -n "$(command -v Xvfb)" ] && ! grep -q "DISABLE_CURSES" "$MESON_BUILD_ROOT/
146146
fi
147147
fi
148148

149-
# Regression test: rmw file/ (trailing slash on regular file) must not move it
149+
# Regression test: rmw file/ (trailing slash on regular file) must move it
150150
echo "$SEPARATOR"
151-
echo "Trailing slash on regular file: must be rejected cleanly"
151+
echo "Trailing slash on regular file: must be moved and restorable"
152152
cd "${RMW_FAKE_HOME}"
153153
touch trailing_slash_file.txt
154-
${RMW_TEST_CMD_STRING} trailing_slash_file.txt/ || true
154+
${RMW_TEST_CMD_STRING} trailing_slash_file.txt/
155+
test ! -f "${RMW_FAKE_HOME}/trailing_slash_file.txt"
156+
test -f "${PRIMARY_WASTE_DIR}/files/trailing_slash_file.txt"
157+
${RMW_TEST_CMD_STRING} -u
155158
test -f "${RMW_FAKE_HOME}/trailing_slash_file.txt"
156159

157160
# Regression test: rmw dir/ (trailing slash) then restore must not create dir/dir

0 commit comments

Comments
 (0)