evil-qemacs/cutils.c
oguillaumin 574cb4ac0f Add utility functions
- `canonicalize_path` returns destination array
  allow for destination to overlap source
- `qe_strcollate` compare directory name before contents
  this fixes the pathnames sorting
- add `get_relativename(filename, dirname)`
2024-08-31 13:49:00 +02:00

585 lines
20 KiB
C

/*
* Various simple C utilities
*
* Copyright (c) 2000-2002 Fabrice Bellard.
* Copyright (c) 2000-2024 Charlie Gordon.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdarg.h>
#include <string.h>
#include "config.h" /* for CONFIG_WIN32 */
#include "cutils.h"
char *pstrcpy(char *buf, int size, const char *str) {
/*@API utils.string
Copy the string pointed by `str` to the destination array `buf`,
of length `size` bytes, truncating excess bytes.
@param `buf` destination array, must be a valid pointer.
@param `size` length of destination array in bytes.
@param `str` pointer to a source string, must be a valid pointer.
@return a pointer to the destination array.
@note: truncation cannot be detected reliably.
@note: this function does what many programmers wrongly expect
`strncpy` to do. `strncpy` has different semantics and does not
null terminate the destination array in case of excess bytes.
**NEVER use `strncpy`**.
*/
if (size > 0) {
int i;
for (i = 0; i < size - 1 && (buf[i] = str[i]) != '\0'; i++)
continue;
buf[i] = '\0';
}
return buf;
}
char *pstrcat(char *buf, int size, const char *s) {
/*@API utils.string
Copy the string pointed by `s` at the end of the string contained
in the destination array `buf`, of length `size` bytes,
truncating excess bytes.
@return a pointer to the destination array.
@note: truncation cannot be detected reliably.
@note: `strncat` has different semantics and does not check
for potential overflow of the destination array.
*/
int len = strnlen(buf, size);
if (len < size)
pstrcpy(buf + len, size - len, s);
return buf;
}
char *pstrncpy(char *buf, int size, const char *s, int len) {
/*@API utils.string
Copy at most `len` bytes from the string pointed by `s` to the
destination array `buf`, of length `size` bytes, truncating
excess bytes.
@return a pointer to the destination array.
@note truncation cannot be detected reliably.
*/
if (size > 0) {
int i;
if (len >= size)
len = size - 1;
for (i = 0; i < len && (buf[i] = s[i]) != '\0'; i++)
continue;
buf[i] = '\0';
}
return buf;
}
char *pstrncat(char *buf, int size, const char *s, int slen) {
/*@API utils.string
Copy at most `len` bytes from the string pointed by `s` at the end
of the string contained in the destination array `buf`, of length
`size` bytes, truncating excess bytes.
@return a pointer to the destination array.
@note truncation cannot be detected reliably.
*/
int len = strnlen(buf, size);
if (len < size)
pstrncpy(buf + len, size - len, s, slen);
return buf;
}
/* these functions are duplicated from ffmpeg/libavformat/cutils.c
* conflict is resolved by redefining the symbols in cutils.h
*/
int strstart(const char *str, const char *val, const char **ptr) {
/*@API utils.string
Test if `val` is a prefix of `str`.
If `val` is a prefix of `str`, a pointer to the first character
after the prefix in `str` is stored into `ptr` provided `ptr`
is not a null pointer.
If `val` is not a prefix of `str`, return `0` and leave `*ptr`
unchanged.
@param `str` input string, must be a valid pointer.
@param `val` prefix string, must be a valid pointer.
@param `ptr` updated with a pointer past the prefix if found.
@return `true` if there is a match, `false` otherwise.
*/
size_t i;
for (i = 0; val[i] != '\0'; i++) {
if (str[i] != val[i])
return 0;
}
if (ptr)
*ptr = &str[i];
return 1;
}
int strend(const char *str, const char *val, const char **ptr) {
/*@API utils.string
Test if `val` is a suffix of `str`.
if `val` is a suffix of `str`, a pointer to the first character
of the suffix in `str` is stored into `ptr` provided `ptr` is
not a null pointer.
@param `str` input string, must be a valid pointer.
@param `val` suffix string, must be a valid pointer.
@param `ptr` updated to the suffix in `str` if there is a match.
@return `true` if there is a match, `false` otherwise.
*/
size_t len1 = strlen(str);
size_t len2 = strlen(val);
if (len1 >= len2 && !memcmp(str += len1 - len2, val, len2)) {
if (ptr)
*ptr = str;
return 1;
} else {
return 0;
}
}
size_t get_basename_offset(const char *path) {
/*@API utils.string
Get the offset of the filename component of a path.
Return the offset to the first character of the filename part of
the path pointed to by string argument `path`.
*/
size_t i, base = 0;
const char *p = path;
if (p) {
for (i = 0; p[i]; i++) {
#ifdef CONFIG_WIN32
/* Simplistic DOS/Windows filename support */
if (p[i] == '/' || p[i] == '\\' || (p[i] == ':' && i == 1))
base = i + 1;
#else
if (p[i] == '/')
base = i + 1;
#endif
}
}
return base;
}
size_t get_extension_offset(const char *path) {
/*@API utils.string
Get the filename extension portion of a path.
Return the offset to the first character of the last extension of
the filename part of the path pointed to by string argument `path`.
If there is no extension, return a pointer to the null terminator
and the end of path.
Leading dots are skipped, they are not considered part of an extension.
*/
size_t ext = 0;
const char *p = path;
if (p) {
size_t i = get_basename_offset(p);
while (p[i] == '.')
i++;
for (; p[i]; i++) {
if (p[i] == '.')
ext = i;
}
if (!ext)
ext = i;
}
return ext;
}
char *get_dirname(char *dest, int size, const char *file) {
/*@API utils.string
Extract the directory portion of a path.
This leaves out the trailing slash if any. The complete path is
obtained by concatenating `dirname` + `"/"` + `basename`.
If the original path doesn't contain a directory name, `"."` is
copied to `dest`.
@return a pointer to the destination array.
@note: truncation cannot be detected reliably.
@note: the trailing slash is not removed if the directory is the
root directory: this makes the behavior somewhat inconsistent,
requiring more tests when reconstructing the full path.
*/
if (dest && size > 0) {
size_t i = 0;
if (file) {
pstrcpy(dest, size, file);
// XXX: should try and detect truncation
i = get_basename_offset(dest);
/* remove the trailing slash (or backslash) unless root dir or
preceded by a drive spec or protocol prefix (eg: http:) */
if (i > 1 && dest[i - 1] != ':' && dest[i - 2] != ':')
i--;
if (i == 0)
dest[i++] = '.';
}
dest[i] = '\0';
}
return dest;
}
const char *get_relativename(const char *filename, const char *dirname) {
/*@API utils.string
Get the offset to the filename porting that is relative to the
directory name `dirname`
@argument `dirname` the name of the directory.
@argument `filename` the name of the file that is a descendent of directory.
@return a pointer inside filename.
*/
const char *ptr;
if (strstart(filename, dirname, &ptr) && *ptr == '/')
return ptr + 1;
return filename;
}
/* Dynamic buffer package */
static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) {
/*@API utils.dbuf
Default memory allocation routine for dynamic buffers.
@argument `opaque` is the opaque argument passed to `dbuf_init2`.
@argument `ptr` is the pointer to the object that must be reallocated.
It is `NULL` if the buffer has not been allocated yet.
@argument `size` is new size requested for the buffer in bytes.
An argument value of `0` for `size` specifies that the block should be
freed and `NULL` will be returned.
@return a pointer to the reallocated block or `NULL` if allocation
failed or the requested size was `0` and the pointer was freed.
@note: the C Standard specifies that if the size is zero, the behavior
of `realloc` is undefined. In FreeBSD systems, if `size` is zero and
`ptr` is not `NULL`, a new, minimum sized object is allocated and the
original object is freed.
*/
if (!size) {
free(ptr);
return NULL;
}
return realloc(ptr, size);
}
DynBuf *dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) {
/*@API utils.dbuf
Initialize a dynamic buffer with a specified reallocation function.
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `opaque` is the opaque argument that will be passed to
the reallocation function.
@argument `realloc_func` the reallocation function to use for this dynamic buffer.
@return the value of `s`.
*/
memset(s, 0, sizeof(*s));
if (!realloc_func)
realloc_func = dbuf_default_realloc;
s->opaque = opaque;
s->realloc_func = realloc_func;
return s;
}
DynBuf *dbuf_init(DynBuf *s) {
/*@API utils.dbuf
Initialize a dynamic buffer with the default reallocation function.
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@return the value of `s`.
*/
return dbuf_init2(s, NULL, NULL);
}
/* return < 0 if error */
int dbuf_realloc(DynBuf *s, size_t new_size) {
/*@API utils.dbuf
Reallocate the buffer to a larger size.
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `new_size` the new size for the buffer. If `new_size` is
smaller than the current buffer length, no reallocation is performed.
@return an error indicator: `0` if buffer was successfully reallocated,
`-1` otherwise and `error` member is set.
@note: the new buffer length may be larger than `new_size` to minimize
further reallocation requests.
*/
size_t size = s->allocated_size;
if (new_size > size) {
uint8_t *new_buf;
if (s->error)
return -1;
size += size / 2;
if (new_size < size)
new_size = size;
new_buf = s->realloc_func(s->opaque, s->buf, new_size);
if (!new_buf) {
s->error = TRUE;
return -1;
}
s->buf = new_buf;
s->allocated_size = new_size;
}
return 0;
}
int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len) {
/*@API utils.dbuf
Write a block of data at a given offset in a dynamic buffer
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `offset` the position where to write the block
@argument `data` a valid pointer to a memory block
@argument `len` the number of bytes to write
@return an error indicator: `0` if data could be written, `-1` if
buffer could not be reallocated.
*/
size_t end;
end = offset + len;
if (dbuf_realloc(s, end))
return -1;
memcpy(s->buf + offset, data, len);
if (end > s->size)
s->size = end;
return 0;
}
int dbuf_put(DynBuf *s, const uint8_t *data, size_t len) {
/*@API utils.dbuf
Write a block of data at the end of a dynamic buffer
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `data` a valid pointer to a memory block
@argument `len` the number of bytes to write
@return an error indicator: `0` if data could be written, `-1` if
buffer could not be reallocated.
*/
if (unlikely((s->size + len) > s->allocated_size)) {
if (dbuf_realloc(s, s->size + len))
return -1;
}
memcpy(s->buf + s->size, data, len);
s->size += len;
return 0;
}
int dbuf_put_self(DynBuf *s, size_t offset, size_t len) {
/*@API utils.dbuf
Duplicate a block of data from the dynamic buffer at the end
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `offset` the offset of the block to copy
@argument `len` the number of bytes to copy
@return an error indicator: `0` if data could be written, `-1` if
buffer could not be reallocated.
*/
if (unlikely((s->size + len) > s->allocated_size)) {
if (dbuf_realloc(s, s->size + len))
return -1;
}
memcpy(s->buf + s->size, s->buf + offset, len);
s->size += len;
return 0;
}
int dbuf_putc(DynBuf *s, uint8_t c) {
/*@API utils.dbuf
Write a byte at the end of a dynamic buffer
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `c` the byte value
@return an error indicator: `0` if data could be written, `-1` if
buffer could not be reallocated.
*/
return dbuf_put(s, &c, 1);
}
int dbuf_putstr(DynBuf *s, const char *str) {
/*@API utils.dbuf
Write a string at the end of a dynamic buffer
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `str` a valid pointer to a string
@return an error indicator: `0` if data could be written, `-1` if
buffer could not be reallocated.
*/
return dbuf_put(s, (const uint8_t *)str, strlen(str));
}
int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, const char *fmt, ...) {
/*@API utils.dbuf
Produce formatted output at the end of a dynamic buffer
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
@argument `fmt` a valid pointer to a format string
@return an error indicator: `0` if data could be written, `-1` if
the buffer could not be reallocated or if there was a formatting error.
*/
va_list ap;
char buf[128];
int len;
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (len < 0)
return len;
if (len < (int)sizeof(buf)) {
/* fast case */
return dbuf_put(s, (uint8_t *)buf, len);
} else {
if (dbuf_realloc(s, s->size + len + 1))
return -1;
va_start(ap, fmt);
vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size,
fmt, ap);
va_end(ap);
s->size += len;
}
return 0;
}
void dbuf_free(DynBuf *s) {
/*@API utils.dbuf
Free the allocated data in a dynamic buffer
@argument `s` a valid pointer to an uninitialized dynamic buffer object.
*/
if (s->buf && s->realloc_func) {
s->realloc_func(s->opaque, s->buf, 0);
}
memset(s, 0, sizeof(*s));
}
int unicode_to_utf8(uint8_t *buf, unsigned int c) {
/*@API utils
Encode a codepoint as UTF-8
@argument `buf` a valid pointer to an array of char at least 6 bytes long
@argument `c` a codepoint
@return the number of bytes produced in the array.
@note: at most 31 bits are encoded, producing at most `UTF8_CHAR_LEN_MAX` bytes.
@note: no null terminator byte is written to the destination array.
*/
uint8_t *q = buf;
if (c < 0x80) {
*q++ = c;
} else {
if (c < 0x800) {
*q++ = (c >> 6) | 0xc0;
} else {
if (c < 0x10000) {
*q++ = (c >> 12) | 0xe0;
} else {
if (c < 0x00200000) {
*q++ = (c >> 18) | 0xf0;
} else {
if (c < 0x04000000) {
*q++ = (c >> 24) | 0xf8;
} else if (c < 0x80000000) {
*q++ = (c >> 30) | 0xfc;
*q++ = ((c >> 24) & 0x3f) | 0x80;
} else {
return 0;
}
*q++ = ((c >> 18) & 0x3f) | 0x80;
}
*q++ = ((c >> 12) & 0x3f) | 0x80;
}
*q++ = ((c >> 6) & 0x3f) | 0x80;
}
*q++ = (c & 0x3f) | 0x80;
}
return q - buf;
}
#if 0
static const int utf8_min_code[5] = {
0x80, 0x800, 0x10000, 0x200000, 0x4000000,
//0x80, 0x20, 0x400, 0x8000, 0x100000,
//0x80, 0x20, 0x10, 0x200, 0x4000,
//0x80, 0x20, 0x10, 0x8, 0x100,
//0x80, 0x20, 0x10, 0x8, 0x4,
};
#endif
int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) {
/*@API utils
Decode a codepoint from a UTF-8 encoded array
@argument `p` a valid pointer to the source array of char
@argument `max_len` the maximum number of bytes to consume,
must be at least `1`.
@argument `pp` a pointer to store the updated value of `p`
@return the codepoint decoded from the array, or `-1` in case of
error. `*pp` is not updated in this case.
@note: the maximum length for a UTF-8 byte sequence is 6 bytes.
*/
// XXX: should return synthetic byte code for invalid UTF-8 encoding
// emacs uses 0x3fff80-0x3fffff for invalid bytes 0x80 to 0xff.
int c, n, b;
c = *p++;
if (c < 0x80) {
*pp = p;
return c;
}
switch (c) {
case 0xc0: case 0xc1:
return -1;
case 0xc2: case 0xc3:
case 0xc4: case 0xc5: case 0xc6: case 0xc7:
case 0xc8: case 0xc9: case 0xca: case 0xcb:
case 0xcc: case 0xcd: case 0xce: case 0xcf:
case 0xd0: case 0xd1: case 0xd2: case 0xd3:
case 0xd4: case 0xd5: case 0xd6: case 0xd7:
case 0xd8: case 0xd9: case 0xda: case 0xdb:
case 0xdc: case 0xdd: case 0xde: case 0xdf:
if (max_len < 2)
return -1;
b = *p++ ^ 0x80;
if (b >= 0x40)
return -1;
c = ((c & 0x1f) << 6) | b;
*pp = p;
return c;
case 0xe0: case 0xe1: case 0xe2: case 0xe3:
case 0xe4: case 0xe5: case 0xe6: case 0xe7:
case 0xe8: case 0xe9: case 0xea: case 0xeb:
case 0xec: case 0xed: case 0xee: case 0xef:
n = 1;
break;
case 0xf0: case 0xf1: case 0xf2: case 0xf3:
case 0xf4: case 0xf5: case 0xf6: case 0xf7:
n = 2;
break;
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
n = 3;
break;
case 0xfc: case 0xfd:
n = 4;
break;
default:
return -1;
}
if (max_len < n + 2)
return -1;
b = *p++ ^ 0x80;
if (b >= 0x40)
return -1;
c &= 0x1f >> n;
c = (c << 6) | b;
if (c < (0x40 >> n))
return -1;
while (n --> 0) {
b = *p++ ^ 0x80;
if (b >= 0x40)
return -1;
c = (c << 6) | b;
}
*pp = p;
return c;
}