From 165232d694fa11768d2b3f19ea28522fa1c9b49f Mon Sep 17 00:00:00 2001 From: Olaf Barthel Date: Mon, 14 Jul 2025 13:29:06 +0200 Subject: [PATCH] The long path handling code works much better if you actually check it in --- library/stdio_long_path.c | 581 ++++++++++++++++++++++++++++++++++++++ library/stdio_long_path.h | 168 +++++++++++ 2 files changed, 749 insertions(+) create mode 100644 library/stdio_long_path.c create mode 100644 library/stdio_long_path.h diff --git a/library/stdio_long_path.c b/library/stdio_long_path.c new file mode 100644 index 0000000..f62d929 --- /dev/null +++ b/library/stdio_long_path.c @@ -0,0 +1,581 @@ +/* + * :ts=4 + * + * Portable ISO 'C' (1994) runtime library for the Amiga computer + * Copyright (c) 2002-2023 by Olaf Barthel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Neither the name of Olaf Barthel nor the names of contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __amigaos4__ + +/****************************************************************************/ + +/* We don't want the replacement functions to become active when buliding + * this file. + */ +#define __DISABLE_LONG_PATHS + +#ifndef _STDIO_LONG_PATH_H +#include "stdio_long_path.h" +#endif /* _STDIO_LONG_PATH_H */ + +/****************************************************************************/ + +#include +#include + +#include + +/****************************************************************************/ + +/* Split an AmigaDOS path name into a sequence of the individual path + * pieces. A path name may be empty, it may include a device name, + * a series of directory names or even "/" references to the + * parent directory and a file, directory or link name. + * + * split_path() is modeled on the SplitName() function in that it + * will copy each path piece into the supplied buffer, returning -1 + * if the last piece has been copied. If -2 is returned, it means + * that the piece buffer is too short for the path piece to be + * copied. + */ +static LONG +split_path(CONST TEXT * path, TEXT * piece, LONG old_position, LONG size) +{ + LONG length_copied = 0; + TEXT c; + + /* The piece has to be NUL-terminated, which reduces the + * usable size of the piece buffer. + */ + size--; + + /* Is the piece buffer too short? */ + if (size <= 0) + return -2; + + /* Is this not an empty string, which stands for the + * current directory? + */ + if (path[0] != '\0') + { + /* Adjust the starting position for scanning the path name, + * which simplifies the loop below. + */ + path += old_position; + + while (size-- >= 0) + { + c = path[length_copied]; + + /* Was this the end of the path name? */ + if (c == '\0') + break; + + (*piece++) = c; + length_copied++; + + /* Is this a device name separator, which may appear + * exactly once in a path name? Then we will have to + * keep it. + */ + if (c == ':' && old_position == 0) + { + break; + } + /* Is there a '/' at the end of this path piece? */ + else if (c == '/') + { + /* Is this in fact a single '/' which represents a + * reference to the parent directory? Then we need + * to keep it, otherwise it will have to be removed. + */ + if (length_copied > 1) + { + /* Make sure that the trailing '/' will + * be removed from the piece. + */ + piece--; + size++; + } + + break; + } + } + + /* Is the piece buffer too short? */ + if (size < 0) + return -2; + } + + /* Make sure that the piece is NUL-terminated. */ + (*piece) = '\0'; + + /* Is the separator character at the end of the path name, + * or is this an empty string (with path_length == 0)? + */ + if (path[length_copied] == '\0') + return -1; /* This is the last piece of the path name. */ + + /* Continue path processing at this position. */ + return old_position + length_copied; +} + +/****************************************************************************/ + +/* If a path name exceeds 255 characters, break down the path into individual + * pieces, traveling to the end of the path and invoke a callback function + * with the current directory set and the name of the file/directory/link + * provided along with additional parameters needed to carry out a file + * system operation. + * + * For path names shorter than 256 characters, no extra work will be done + * and the callback function will be invoked directly. + */ + +typedef LONG (*callback_f)(CONST TEXT * name, LONG arg1, LONG arg2); + +static void +long_path_workaround( + CONST TEXT * path_name, LONG arg1, LONG arg2, + LONG * result_ptr, + callback_f callback) +{ + TEXT piece[256]; + LONG path_name_len; + LONG len; + CONST TEXT * colon; + BPTR reference_dir = (BPTR)NULL; + BPTR old_current_dir = (BPTR)NULL; + LONG position = 0; + BPTR lock; + + path_name_len = strlen(path_name); + + /* Only go through with the extra effort to resolve the path + * if this job requires a file system and not a handler (such + * as "NIL:" or "CON:") and if the path name exceeds 255 + * characters. + */ + if (path_name_len > 255 && IsFileSystem(path_name)) + { + /* Does the path begin with a device name, or a reference + * to the root directory of a volume? + */ + colon = strchr(path_name, ':'); + if (colon != NULL) + { + /* We need to copy the name of the device, which must + * not exceed 255 characters, including the colon + * character. Volume and device names should never + * exceed 30 characters, but accidents may still + * happen... + */ + len = (LONG)(colon - path_name) + 1; + if (len > 255) + { + SetIoErr(ERROR_LINE_TOO_LONG); + goto out; + } + + /* Obtain a shared lock on the device or root + * directory. We are using Lock() because it + * implies that the handler is a file system + * and not, for example, "NIL:" or "CON:". + */ + CopyMem(path_name, piece, len); + piece[len] = '\0'; + + reference_dir = Lock(piece, SHARED_LOCK); + if (reference_dir == (BPTR)NULL) + goto out; + + /* We took care of the device or root directory name. */ + path_name += len; + } + + /* Process the path name, one piece at a time, relative + * to a Lock on its parent directory. This repeats + * until the last piece of the path has been found. + * Each loop iteration will reuse the Lock of the + * previous iteration as the parent directory. + */ + while (TRUE) + { + /* Obtain the next piece of the path. */ + position = split_path(path_name, piece, position, sizeof(piece)); + + /* Is the piece too long? */ + if (position == -2) + { + SetIoErr(ERROR_LINE_TOO_LONG); + goto out; + } + + /* Access the next piece of the path relative to a + * specific directory? Otherwise, we use the current + * directory of the current Process. Because the current + * directory may be an exclusive lock we cannot + * conveniently fall back onto DupLock() or + * Lock("", SHARED_LOCK) instead. + */ + if (reference_dir != (BPTR)NULL) + old_current_dir = CurrentDir(reference_dir); + + /* Unless this is the final piece of the path + * (split_path() will return -1), we will need to call + * Lock() and make use of shared locks. + */ + if (position != -1) + { + lock = Lock(piece, SHARED_LOCK); + } + else + { + lock = (BPTR)NULL; + + /* Time to do what we came here for. */ + (*result_ptr) = (*callback)(piece, arg1, arg2); + } + + if (reference_dir != (BPTR)NULL) + CurrentDir(old_current_dir); + + /* Stop as soon as we reach the end of the path or + * if the Lock could not be obtained. + */ + if (position == -1 || lock == (BPTR)NULL) + break; + + /* Look at the next piece of the remaining path. + * Free the current reference directory Lock and + * replace it with the Lock obtained above. + */ + UnLock(reference_dir); + reference_dir = lock; + } + } + else + { + (*result_ptr) = (*callback)(path_name, arg1, arg2); + } + + out: + + UnLock(reference_dir); +} + +/****************************************************************************/ + +static LONG +lock_callback(CONST TEXT * name, LONG type) +{ + return Lock(name, type); +} + +BPTR +__new_lock(CONST TEXT * name, LONG type ) +{ + BPTR result = (BPTR)NULL; + + long_path_workaround(name, type, 0, (LONG *)&result, (callback_f)lock_callback); + + return result; +} + +/****************************************************************************/ + +static LONG +set_owner_callback(CONST TEXT * name, LONG owner_info) +{ + return SetOwner(name, owner_info); +} + +LONG +__new_set_owner(CONST TEXT * name, LONG owner_info) +{ + LONG success = FALSE; + + long_path_workaround(name, owner_info, 0, &success, (callback_f)set_owner_callback); + + return success; +} + +/****************************************************************************/ + +static LONG +set_comment_callback(CONST TEXT * name, LONG comment) +{ + return SetComment(name, (STRPTR)comment); +} + +LONG +__new_set_comment(CONST TEXT * name, CONST TEXT * comment) +{ + LONG success = FALSE; + + long_path_workaround(name, (LONG)comment, 0, &success, (callback_f)set_comment_callback); + + return success; +} + +/****************************************************************************/ + +static LONG +set_protect_callback(CONST TEXT * name, LONG protect) +{ + return SetProtection(name, protect); +} + +LONG +__new_set_protection(CONST TEXT * name, LONG protect) +{ + LONG success = FALSE; + + long_path_workaround(name, protect, 0, &success, (callback_f)set_protect_callback); + + return success; +} + +/****************************************************************************/ + +static LONG +set_file_date_callback(CONST TEXT * name, LONG date) +{ + return SetFileDate(name, (struct DateStamp *)date); +} + +LONG +__new_set_file_date(CONST TEXT * name, CONST struct DateStamp * date) +{ + LONG success = FALSE; + + long_path_workaround(name, (LONG)date, 0, &success, (callback_f)set_file_date_callback); + + return success; +} + +/****************************************************************************/ + +static LONG +open_callback(CONST TEXT * name, LONG mode) +{ + return Open(name, mode); +} + +BPTR +__new_open(CONST TEXT * name, LONG mode) +{ + BPTR file = (BPTR)NULL; + + long_path_workaround(name, mode, 0, (LONG *)&file, (callback_f)open_callback); + + return file; +} + +/****************************************************************************/ + +static LONG +delete_file_callback(CONST TEXT * name) +{ + return DeleteFile(name); +} + +LONG +__new_delete_file(CONST TEXT * name) +{ + LONG successful = FALSE; + + long_path_workaround(name, 0, 0, &successful, (callback_f)delete_file_callback); + + return successful; +} + +/****************************************************************************/ + +static LONG +create_dir_callback(CONST TEXT * name) +{ + return CreateDir(name); +} + +BPTR +__new_create_dir(CONST TEXT * name) +{ + BPTR lock = (BPTR)NULL; + + long_path_workaround(name, 0, 0, (LONG *)&lock, (callback_f)create_dir_callback); + + return lock; +} + +/****************************************************************************/ + +static LONG +make_link_callback(CONST TEXT * name, LONG dest, LONG soft) +{ + return MakeLink(name, dest, soft); +} + +LONG +__new_make_link(CONST TEXT * name, LONG dest, LONG soft) +{ + LONG success = FALSE; + + long_path_workaround(name, dest, soft, (LONG *)&success, (callback_f)make_link_callback); + + return success; +} + +/****************************************************************************/ + +struct rename_arg +{ + BPTR dir_lock; + TEXT name[256+4]; +}; + +static struct rename_arg * +create_rename_arg(void) +{ + struct rename_arg * arg; + + arg = AllocMem(sizeof(*arg), MEMF_PUBLIC); + if (arg != NULL) + arg->dir_lock = (BPTR)NULL; + + return arg; +} + +static void +delete_rename_arg(struct rename_arg * arg) +{ + if (arg != NULL) + { + UnLock(arg->dir_lock); + FreeMem(arg, sizeof(*arg)); + } +} + +static LONG +rename_callback(CONST TEXT * name, struct rename_arg * arg) +{ + BOOL successful = FALSE; + LONG name_len; + BPTR old_lock; + + old_lock = CurrentDir((BPTR)NULL); + CurrentDir(old_lock); + + if (old_lock != (BPTR)NULL) + { + arg->dir_lock = DupLock(old_lock); + if (arg->dir_lock != (BPTR)NULL) + successful = TRUE; + } + else + { + successful = TRUE; + } + + if (successful) + { + name_len = strlen(name); + if (name_len > 255) + name_len = 255; + + arg->name[0] = name_len; + CopyMem(name, &arg->name[1], name_len); + arg->name[1+name_len] = '\0'; + } + + return successful; +} + +LONG +__new_rename(CONST TEXT * oldName, CONST TEXT * newName) +{ + struct rename_arg * from = NULL; + struct rename_arg * to = NULL; + struct MsgPort * filesys_port; + LONG successful = FALSE; + + if (IsFileSystem(oldName) && IsFileSystem(newName) && (strlen(oldName) > 255 || strlen(newName) > 255)) + { + from = create_rename_arg(); + if (from == NULL) + goto out; + + to = create_rename_arg(); + if (to == NULL) + goto out; + + long_path_workaround(oldName, (LONG)from, 0, (LONG *)&successful, (callback_f)rename_callback); + if (successful == FALSE) + goto out; + + long_path_workaround(newName, (LONG)to, 0, (LONG *)&successful, (callback_f)rename_callback); + if (successful == FALSE) + goto out; + + if (from->dir_lock == (BPTR)NULL && to->dir_lock == (BPTR)NULL) + { + filesys_port = GetFileSysTask(); + } + else + { + if (from->dir_lock == (BPTR)NULL || to->dir_lock == (BPTR)NULL || + SameLock(from->dir_lock, to->dir_lock) == LOCK_DIFFERENT) + { + SetIoErr(ERROR_RENAME_ACROSS_DEVICES); + + successful = FALSE; + goto out; + } + + filesys_port = ((struct FileLock *)BADDR(from->dir_lock))->fl_Task; + } + + successful = DoPkt4(filesys_port, ACTION_RENAME_OBJECT, + from->dir_lock, MKBADDR(from->name), + to->dir_lock, MKBADDR(to->name)); + } + else + { + successful = Rename(oldName, newName); + } + + out: + + delete_rename_arg(from); + delete_rename_arg(to); + + return successful; +} + +/****************************************************************************/ + +#endif /* __amigaos4__ */ diff --git a/library/stdio_long_path.h b/library/stdio_long_path.h new file mode 100644 index 0000000..cb9aa35 --- /dev/null +++ b/library/stdio_long_path.h @@ -0,0 +1,168 @@ +/* + * :ts=4 + * + * Portable ISO 'C' (1994) runtime library for the Amiga computer + * Copyright (c) 2002-2023 by Olaf Barthel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Neither the name of Olaf Barthel nor the names of contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _STDIO_LONG_PATH_H +#define _STDIO_LONG_PATH_H + +/****************************************************************************/ + +/* This option enables support for long path names under Kickstart 2.04 + * and beyond. Because this workaround is not needed for AmigaOS4, it will + * be ignored if you try to enable it. + * + * Caution: Enable this feature only here in the "stdio_headers.h" file, + * never as a compiler option. This is to make sure that all the + * source code which makes use of this feature is rebuilt when + * you change this file. + */ +/*#define __USE_LONG_PATHS*/ + +/****************************************************************************/ + +#ifndef __amigaos4__ + +/****************************************************************************/ + +#ifndef PROTO_DOS_H +#include +#endif /* PROTO_DOS_H */ + +/****************************************************************************/ + +extern BPTR __new_lock(CONST TEXT * name, LONG type ); +extern LONG __new_set_owner(CONST TEXT * name, LONG owner_info); +extern LONG __new_set_comment(CONST TEXT * name, CONST TEXT * comment); +extern LONG __new_set_protection(CONST TEXT * name, LONG protect); +extern LONG __new_set_file_date(CONST TEXT * name, CONST struct DateStamp * date); +extern BPTR __new_open(CONST TEXT * name, LONG mode); +extern LONG __new_delete_file(CONST TEXT * name); +extern BPTR __new_create_dir(CONST TEXT * name); +extern LONG __new_make_link(CONST TEXT * name, LONG dest, LONG soft); +extern LONG __new_rename(CONST TEXT * oldName, CONST TEXT * newName); + +/****************************************************************************/ + +/* We don't want the replacement functions to become active when building + * the "stdio_long_path.c" file. + */ +#if ! defined(__DISABLE_LONG_PATHS) && defined(__USE_LONG_PATHS) + +/****************************************************************************/ + +#if defined(Lock) +#undef Lock +#endif /* Lock */ + +#define Lock(name, type) __new_lock((name), (type)) + +/****************************************************************************/ + +#if defined(SetOwner) +#undef SetOwner +#endif /* SetOwner */ + +#define SetOwner(name, owner_info) __new_set_owner((name), (owner_info)) + +/****************************************************************************/ + +#if defined(SetComment) +#undef SetComment +#endif /* SetComment */ + +#define SetComment(name, comment) __new_set_comment((name), (comment)) + +/****************************************************************************/ + +#if defined(SetProtection) +#undef SetProtection +#endif /* SetProtection */ + +#define SetProtection(name, protect) __new_set_protection((name), (protect)) + +/****************************************************************************/ + +#if defined(SetFileDate) +#undef SetFileDate +#endif /* SetFileDate */ + +#define SetFileDate(name, date) __new_set_file_date((name), (date)) + +/****************************************************************************/ + +#if defined(Open) +#undef Open +#endif /* Open */ + +#define Open(name, mode) __new_open((name), (mode)) + +/****************************************************************************/ + +#if defined(DeleteFile) +#undef DeleteFile +#endif /* DeleteFile */ + +#define DeleteFile(name) __new_delete_file(name) + +/****************************************************************************/ + +#if defined(CreateDir) +#undef CreateDir +#endif /* CreateDir */ + +#define CreateDir(name) __new_create_dir(name) + +/****************************************************************************/ + +#if defined(MakeLink) +#undef MakeLink +#endif /* MakeLink */ + +#define MakeLink(name, dest, soft) __new_make_link((name), (dest), (soft)) + +/****************************************************************************/ + +#if defined(Rename) +#undef Rename +#endif /* Rename */ + +#define Rename(oldName, newName) __new_rename((oldName), (newName)) + +/****************************************************************************/ + +#endif /* !__DISABLE_LONG_PATHS && __USE_LONG_PATHS */ + +/****************************************************************************/ + +#endif /* __amigaos4__ */ + +/****************************************************************************/ + +#endif /* _STDIO_LONG_PATH_H */