diff options
Diffstat (limited to 'misc')
-rw-r--r-- | misc/bstr.c | 20 | ||||
-rw-r--r-- | misc/bstr.h | 8 | ||||
-rw-r--r-- | misc/io_utils.c | 87 | ||||
-rw-r--r-- | misc/io_utils.h | 25 | ||||
-rw-r--r-- | misc/jni.c | 300 | ||||
-rw-r--r-- | misc/jni.h | 16 | ||||
-rw-r--r-- | misc/path_utils.c | 219 | ||||
-rw-r--r-- | misc/path_utils.h | 64 | ||||
-rw-r--r-- | misc/rendezvous.c | 2 |
9 files changed, 564 insertions, 177 deletions
diff --git a/misc/bstr.c b/misc/bstr.c index 4f1e862..abe688b 100644 --- a/misc/bstr.c +++ b/misc/bstr.c @@ -467,3 +467,23 @@ bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out) *out = (struct bstr){ .start = arr, .len = len }; return true; } + +#ifdef _WIN32 + +#include <windows.h> + +int bstr_to_wchar(void *talloc_ctx, struct bstr s, wchar_t **ret) +{ + int count = MultiByteToWideChar(CP_UTF8, 0, s.start, s.len, NULL, 0); + if (count <= 0) + abort(); + wchar_t *wbuf = *ret; + if (!wbuf || ta_get_size(wbuf) < (count + 1) * sizeof(wchar_t)) + wbuf = talloc_realloc(talloc_ctx, wbuf, wchar_t, count + 1); + MultiByteToWideChar(CP_UTF8, 0, s.start, s.len, wbuf, count); + wbuf[count] = L'\0'; + *ret = wbuf; + return count; +} + +#endif diff --git a/misc/bstr.h b/misc/bstr.h index dc8ad40..aaae7d6 100644 --- a/misc/bstr.h +++ b/misc/bstr.h @@ -56,6 +56,8 @@ static inline struct bstr bstrdup(void *talloc_ctx, struct bstr str) return r; } +#define bstr0_lit(s) {(unsigned char *)(s), sizeof("" s) - 1} + static inline struct bstr bstr0(const char *s) { return (struct bstr){(unsigned char *)s, s ? strlen(s) : 0}; @@ -223,6 +225,12 @@ static inline bool bstr_eatend0(struct bstr *s, const char *prefix) return bstr_eatend(s, bstr0(prefix)); } +#ifdef _WIN32 + +int bstr_to_wchar(void *talloc_ctx, struct bstr s, wchar_t **ret); + +#endif + // create a pair (not single value!) for "%.*s" printf syntax #define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "") diff --git a/misc/io_utils.c b/misc/io_utils.c new file mode 100644 index 0000000..c973cee --- /dev/null +++ b/misc/io_utils.c @@ -0,0 +1,87 @@ +/* + * I/O utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/types.h> +#include <limits.h> +#include <unistd.h> + +#include "mpv_talloc.h" +#include "config.h" +#include "misc/random.h" +#include "misc/io_utils.h" +#include "osdep/io.h" + +int mp_mkostemps(char *template, int suffixlen, int flags) +{ + size_t len = strlen(template); + char *t = len >= 6 + suffixlen ? &template[len - (6 + suffixlen)] : NULL; + if (!t || strncmp(t, "XXXXXX", 6) != 0) { + errno = EINVAL; + return -1; + } + + for (size_t fuckshit = 0; fuckshit < UINT32_MAX; fuckshit++) { + // Using a random value may make it require fewer iterations (even if + // not truly random; just a counter would be sufficient). + size_t fuckmess = mp_rand_next(); + char crap[7] = ""; + snprintf(crap, sizeof(crap), "%06zx", fuckmess); + memcpy(t, crap, 6); + + int res = open(template, O_RDWR | O_CREAT | O_EXCL | flags, 0600); + if (res >= 0 || errno != EEXIST) + return res; + } + + errno = EEXIST; + return -1; +} + +bool mp_save_to_file(const char *filepath, const void *data, size_t size) +{ + assert(filepath && data && size); + + bool result = false; + char *tmp = talloc_asprintf(NULL, "%sXXXXXX", filepath); + int fd = mkstemp(tmp); + if (fd < 0) + goto done; + FILE *cache = fdopen(fd, "wb"); + if (!cache) { + close(fd); + unlink(tmp); + goto done; + } + size_t written = fwrite(data, size, 1, cache); + int ret = fclose(cache); + if (written > 0 && !ret) { + ret = rename(tmp, filepath); + result = !ret; + } else { + unlink(tmp); + } + +done: + talloc_free(tmp); + return result; +} diff --git a/misc/io_utils.h b/misc/io_utils.h new file mode 100644 index 0000000..012f242 --- /dev/null +++ b/misc/io_utils.h @@ -0,0 +1,25 @@ +/* + * I/O utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stddef.h> + +int mp_mkostemps(char *template, int suffixlen, int flags); +bool mp_save_to_file(const char *filepath, const void *data, size_t size); @@ -20,11 +20,10 @@ */ #include <libavcodec/jni.h> -#include <libavutil/mem.h> -#include <libavutil/bprint.h> #include <stdlib.h> #include "jni.h" +#include "mpv_talloc.h" #include "osdep/threads.h" static JavaVM *java_vm; @@ -46,13 +45,11 @@ static void jni_create_pthread_key(void) JNIEnv *mp_jni_get_env(struct mp_log *log) { - int ret = 0; JNIEnv *env = NULL; mp_mutex_lock(&lock); - if (java_vm == NULL) { + if (!java_vm) java_vm = av_jni_get_java_vm(NULL); - } if (!java_vm) { mp_err(log, "No Java virtual machine has been registered\n"); @@ -61,11 +58,10 @@ JNIEnv *mp_jni_get_env(struct mp_log *log) mp_exec_once(&once, jni_create_pthread_key); - if ((env = pthread_getspecific(current_env)) != NULL) { + if ((env = pthread_getspecific(current_env)) != NULL) goto done; - } - ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6); + int ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6); switch(ret) { case JNI_EDETACHED: if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) { @@ -92,39 +88,27 @@ done: char *mp_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, struct mp_log *log) { - char *ret = NULL; - const char *utf_chars = NULL; - - jboolean copy = 0; - - if (!string) { + if (!string) return NULL; - } - utf_chars = (*env)->GetStringUTFChars(env, string, ©); + const char *utf_chars = (*env)->GetStringUTFChars(env, string, NULL); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); - mp_err(log, "String.getStringUTFChars() threw an exception\n"); + mp_err(log, "getStringUTFChars() threw an exception\n"); return NULL; } - ret = av_strdup(utf_chars); + char *ret = talloc_strdup(NULL, utf_chars); (*env)->ReleaseStringUTFChars(env, string, utf_chars); - if ((*env)->ExceptionCheck(env)) { - (*env)->ExceptionClear(env); - mp_err(log, "String.releaseStringUTFChars() threw an exception\n"); - return NULL; - } return ret; } -jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct mp_log *log) +jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, + struct mp_log *log) { - jstring ret; - - ret = (*env)->NewStringUTF(env, utf_chars); + jstring ret = (*env)->NewStringUTF(env, utf_chars); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); mp_err(log, "NewStringUTF() threw an exception\n"); @@ -134,24 +118,19 @@ jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct m return ret; } -int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, struct mp_log *log) +int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, + char **error, struct mp_log *log) { int ret = 0; - AVBPrint bp; - char *name = NULL; char *message = NULL; jclass class_class = NULL; - jmethodID get_name_id = NULL; - jclass exception_class = NULL; - jmethodID get_message_id = NULL; - jstring string = NULL; - av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + *error = NULL; exception_class = (*env)->GetObjectClass(env, exception); if ((*env)->ExceptionCheck(env)) { @@ -169,7 +148,7 @@ int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error goto done; } - get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;"); + jmethodID get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;"); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); mp_err(log, "Could not find method Class.getName()\n"); @@ -187,14 +166,13 @@ int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error if (string) { name = mp_jni_jstring_to_utf_chars(env, string, log); - (*env)->DeleteLocalRef(env, string); - string = NULL; + MP_JNI_LOCAL_FREEP(&string); } - get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;"); + jmethodID get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;"); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); - mp_err(log, "Could not find method java/lang/Throwable.getMessage()\n"); + mp_err(log, "Could not find method Throwable.getMessage()\n"); ret = -1; goto done; } @@ -209,164 +187,145 @@ int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error if (string) { message = mp_jni_jstring_to_utf_chars(env, string, log); - (*env)->DeleteLocalRef(env, string); - string = NULL; + MP_JNI_LOCAL_FREEP(&string); } if (name && message) { - av_bprintf(&bp, "%s: %s", name, message); + *error = talloc_asprintf(NULL, "%s: %s", name, message); } else if (name && !message) { - av_bprintf(&bp, "%s occurred", name); + *error = talloc_asprintf(NULL, "%s occurred", name); } else if (!name && message) { - av_bprintf(&bp, "Exception: %s", message); + *error = talloc_asprintf(NULL, "Exception: %s", message); } else { mp_warn(log, "Could not retrieve exception name and message\n"); - av_bprintf(&bp, "Exception occurred"); + *error = talloc_strdup(NULL, "Exception occurred"); } - ret = av_bprint_finalize(&bp, error); done: - av_free(name); - av_free(message); - - if (class_class) { - (*env)->DeleteLocalRef(env, class_class); - } - - if (exception_class) { - (*env)->DeleteLocalRef(env, exception_class); - } + talloc_free(name); + talloc_free(message); - if (string) { - (*env)->DeleteLocalRef(env, string); - } + MP_JNI_LOCAL_FREEP(&class_class); + MP_JNI_LOCAL_FREEP(&exception_class); + MP_JNI_LOCAL_FREEP(&string); return ret; } int mp_jni_exception_check(JNIEnv *env, int logging, struct mp_log *log) { - int ret; - - jthrowable exception; - - char *message = NULL; - - if (!(*(env))->ExceptionCheck((env))) { + if (!(*env)->ExceptionCheck(env)) return 0; - } if (!logging) { - (*(env))->ExceptionClear((env)); + (*env)->ExceptionClear(env); return -1; } - exception = (*env)->ExceptionOccurred(env); - (*(env))->ExceptionClear((env)); + jthrowable exception = (*env)->ExceptionOccurred(env); + (*env)->ExceptionClear(env); - if ((ret = mp_jni_exception_get_summary(env, exception, &message, log)) < 0) { - (*env)->DeleteLocalRef(env, exception); + char *message = NULL; + int ret = mp_jni_exception_get_summary(env, exception, &message, log); + MP_JNI_LOCAL_FREEP(&exception); + if (ret < 0) return ret; - } - - (*env)->DeleteLocalRef(env, exception); mp_err(log, "%s\n", message); - av_free(message); - + talloc_free(message); return -1; } -int mp_jni_init_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log) +#define CHECK_EXC_MANDATORY() do { \ + if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && \ + mandatory) { \ + goto done; \ + } \ + } while (0) + +int mp_jni_init_jfields(JNIEnv *env, void *jfields, + const struct MPJniField *jfields_mapping, + int global, struct mp_log *log) { - int i, ret = 0; + int ret = 0; jclass last_clazz = NULL; - for (i = 0; jfields_mapping[i].name; i++) { - int mandatory = jfields_mapping[i].mandatory; + for (int i = 0; jfields_mapping[i].name; i++) { + bool mandatory = !!jfields_mapping[i].mandatory; enum MPJniFieldType type = jfields_mapping[i].type; - if (type == MP_JNI_CLASS) { - jclass clazz; + void *jfield = (uint8_t*)jfields + jfields_mapping[i].offset; + if (type == MP_JNI_CLASS) { last_clazz = NULL; - clazz = (*env)->FindClass(env, jfields_mapping[i].name); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + jclass clazz = (*env)->FindClass(env, jfields_mapping[i].name); + CHECK_EXC_MANDATORY(); - last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = + last_clazz = *(jclass*)jfield = global ? (*env)->NewGlobalRef(env, clazz) : clazz; - if (global) { - (*env)->DeleteLocalRef(env, clazz); - } - - } else { + if (global) + MP_JNI_LOCAL_FREEP(&clazz); - if (!last_clazz) { - ret = -1; - break; - } + continue; + } - switch(type) { - case MP_JNI_FIELD: { - jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + if (!last_clazz) { + ret = -1; + break; + } - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id; - break; - } - case MP_JNI_STATIC_FIELD_AS_INT: - case MP_JNI_STATIC_FIELD: { - jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + switch (type) { + case MP_JNI_FIELD: { + jfieldID field_id = (*env)->GetFieldID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); - if (type == MP_JNI_STATIC_FIELD_AS_INT) { - if (field_id) { - jint value = (*env)->GetStaticIntField(env, last_clazz, field_id); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } - *(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = value; - } - } else { - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id; - } - break; - } - case MP_JNI_METHOD: { - jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; + *(jfieldID*)jfield = field_id; + break; + } + case MP_JNI_STATIC_FIELD_AS_INT: + case MP_JNI_STATIC_FIELD: { + jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); + + if (type == MP_JNI_STATIC_FIELD_AS_INT) { + if (field_id) { + jint value = (*env)->GetStaticIntField(env, last_clazz, field_id); + CHECK_EXC_MANDATORY(); + *(jint*)jfield = value; } - - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id; - break; + } else { + *(jfieldID*)jfield = field_id; } - case MP_JNI_STATIC_METHOD: { - jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature); - if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) { - goto done; - } + break; + } + case MP_JNI_METHOD: { + jmethodID method_id = (*env)->GetMethodID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id; - break; - } - default: - mp_err(log, "Unknown JNI field type\n"); - ret = -1; - goto done; - } + *(jmethodID*)jfield = method_id; + break; + } + case MP_JNI_STATIC_METHOD: { + jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, + jfields_mapping[i].name, jfields_mapping[i].signature); + CHECK_EXC_MANDATORY(); - ret = 0; + *(jmethodID*)jfield = method_id; + break; + } + default: + mp_err(log, "Unknown JNI field type\n"); + ret = -1; + goto done; } + + ret = 0; } done: @@ -378,48 +337,43 @@ done: return ret; } -int mp_jni_reset_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log) -{ - int i; +#undef CHECK_EXC_MANDATORY - for (i = 0; jfields_mapping[i].name; i++) { +int mp_jni_reset_jfields(JNIEnv *env, void *jfields, + const struct MPJniField *jfields_mapping, + int global, struct mp_log *log) +{ + for (int i = 0; jfields_mapping[i].name; i++) { enum MPJniFieldType type = jfields_mapping[i].type; - switch(type) { + void *jfield = (uint8_t*)jfields + jfields_mapping[i].offset; + + switch (type) { case MP_JNI_CLASS: { - jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset); + jclass clazz = *(jclass*)jfield; if (!clazz) continue; if (global) { - (*env)->DeleteGlobalRef(env, clazz); + MP_JNI_GLOBAL_FREEP(&clazz); } else { - (*env)->DeleteLocalRef(env, clazz); + MP_JNI_LOCAL_FREEP(&clazz); } - *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; - break; - } - case MP_JNI_FIELD: { - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; - break; - } - case MP_JNI_STATIC_FIELD: { - *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; + *(jclass*)jfield = NULL; break; } - case MP_JNI_STATIC_FIELD_AS_INT: { - *(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = 0; + case MP_JNI_FIELD: + case MP_JNI_STATIC_FIELD: + *(jfieldID*)jfield = NULL; break; - } - case MP_JNI_METHOD: { - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; + case MP_JNI_STATIC_FIELD_AS_INT: + *(jint*)jfield = 0; break; - } - case MP_JNI_STATIC_METHOD: { - *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL; + case MP_JNI_METHOD: + case MP_JNI_STATIC_METHOD: + *(jmethodID*)jfield = NULL; break; - } default: mp_err(log, "Unknown JNI field type\n"); } @@ -22,6 +22,7 @@ #ifndef MP_JNI_H #define MP_JNI_H +#include <stdbool.h> #include <jni.h> #include "common/msg.h" @@ -29,7 +30,7 @@ #define MP_JNI_GET_ENV(obj) mp_jni_get_env((obj)->log) #define MP_JNI_EXCEPTION_CHECK() mp_jni_exception_check(env, 0, NULL) #define MP_JNI_EXCEPTION_LOG(obj) mp_jni_exception_check(env, 1, (obj)->log) -#define MP_JNI_DO(what, obj, method, ...) (*env)->what(env, obj, method, ##__VA_ARGS__) +#define MP_JNI_DO(what, obj, ...) (*env)->what(env, obj, ##__VA_ARGS__) #define MP_JNI_NEW(clazz, method, ...) MP_JNI_DO(NewObject, clazz, method, ##__VA_ARGS__) #define MP_JNI_CALL_INT(obj, method, ...) MP_JNI_DO(CallIntMethod, obj, method, ##__VA_ARGS__) #define MP_JNI_CALL_BOOL(obj, method, ...) MP_JNI_DO(CallBooleanMethod, obj, method, ##__VA_ARGS__) @@ -39,6 +40,16 @@ #define MP_JNI_GET_INT(obj, field) MP_JNI_DO(GetIntField, obj, field) #define MP_JNI_GET_LONG(obj, field) MP_JNI_DO(GetLongField, obj, field) #define MP_JNI_GET_BOOL(obj, field) MP_JNI_DO(GetBoolField, obj, field) +#define MP_JNI_LOCAL_FREEP(objp) do { \ + if (*(objp)) \ + MP_JNI_DO(DeleteLocalRef, *(objp)); \ + *(objp) = NULL; \ + } while (0) +#define MP_JNI_GLOBAL_FREEP(objp) do { \ + if (*(objp)) \ + MP_JNI_DO(DeleteGlobalRef, *(objp)); \ + *(objp) = NULL; \ + } while (0) /* * Attach permanently a JNI environment to the current thread and retrieve it. @@ -118,11 +129,10 @@ enum MPJniFieldType { struct MPJniField { const char *name; - const char *method; const char *signature; enum MPJniFieldType type; int offset; - int mandatory; + bool mandatory; }; diff --git a/misc/path_utils.c b/misc/path_utils.c new file mode 100644 index 0000000..14b4a97 --- /dev/null +++ b/misc/path_utils.c @@ -0,0 +1,219 @@ +/* + * Path utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include "config.h" + +#include "mpv_talloc.h" +#include "osdep/io.h" +#include "misc/ctype.h" +#include "misc/path_utils.h" + +char *mp_basename(const char *path) +{ + char *s; + +#if HAVE_DOS_PATHS + if (!mp_is_url(bstr0(path))) { + s = strrchr(path, '\\'); + if (s) + path = s + 1; + s = strrchr(path, ':'); + if (s) + path = s + 1; + } +#endif + s = strrchr(path, '/'); + return s ? s + 1 : (char *)path; +} + +struct bstr mp_dirname(const char *path) +{ + struct bstr ret = { + (uint8_t *)path, mp_basename(path) - path + }; + if (ret.len == 0) + return bstr0("."); + return ret; +} + + +#if HAVE_DOS_PATHS +static const char mp_path_separators[] = "\\/"; +#else +static const char mp_path_separators[] = "/"; +#endif + +// Mutates path and removes a trailing '/' (or '\' on Windows) +void mp_path_strip_trailing_separator(char *path) +{ + size_t len = strlen(path); + if (len > 0 && strchr(mp_path_separators, path[len - 1])) + path[len - 1] = '\0'; +} + +char *mp_splitext(const char *path, bstr *root) +{ + assert(path); + int skip = (*path == '.'); // skip leading dot for "hidden" unix files + const char *split = strrchr(path + skip, '.'); + if (!split || !split[1] || strchr(split, '/')) + return NULL; + if (root) + *root = (bstr){(char *)path, split - path}; + return (char *)split + 1; +} + +bool mp_path_is_absolute(struct bstr path) +{ + if (path.len && strchr(mp_path_separators, path.start[0])) + return true; + +#if HAVE_DOS_PATHS + // Note: "X:filename" is a path relative to the current working directory + // of drive X, and thus is not an absolute path. It needs to be + // followed by \ or /. + if (path.len >= 3 && path.start[1] == ':' && + strchr(mp_path_separators, path.start[2])) + return true; +#endif + + return false; +} + +char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2) +{ + if (p1.len == 0) + return bstrdup0(talloc_ctx, p2); + if (p2.len == 0) + return bstrdup0(talloc_ctx, p1); + + if (mp_path_is_absolute(p2)) + return bstrdup0(talloc_ctx, p2); + + bool have_separator = strchr(mp_path_separators, p1.start[p1.len - 1]); +#if HAVE_DOS_PATHS + // "X:" only => path relative to "X:" current working directory. + if (p1.len == 2 && p1.start[1] == ':') + have_separator = true; +#endif + + return talloc_asprintf(talloc_ctx, "%.*s%s%.*s", BSTR_P(p1), + have_separator ? "" : "/", BSTR_P(p2)); +} + +char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2) +{ + return mp_path_join_bstr(talloc_ctx, bstr0(p1), bstr0(p2)); +} + +char *mp_getcwd(void *talloc_ctx) +{ + char *e_wd = getenv("PWD"); + if (e_wd) + return talloc_strdup(talloc_ctx, e_wd); + + char *wd = talloc_array(talloc_ctx, char, 20); + while (getcwd(wd, talloc_get_size(wd)) == NULL) { + if (errno != ERANGE) { + talloc_free(wd); + return NULL; + } + wd = talloc_realloc(talloc_ctx, wd, char, talloc_get_size(wd) * 2); + } + return wd; +} + +char *mp_normalize_path(void *talloc_ctx, const char *path) +{ + if (mp_is_url(bstr0(path))) + return talloc_strdup(talloc_ctx, path); + + return mp_path_join(talloc_ctx, mp_getcwd(talloc_ctx), path); +} + +bool mp_path_exists(const char *path) +{ + struct stat st; + return path && stat(path, &st) == 0; +} + +bool mp_path_isdir(const char *path) +{ + struct stat st; + return stat(path, &st) == 0 && S_ISDIR(st.st_mode); +} + +// Return false if it's considered a normal local filesystem path. +bool mp_is_url(bstr path) +{ + int proto = bstr_find0(path, "://"); + if (proto < 1) + return false; + // Per RFC3986, the first character of the protocol must be alphabetic. + // The rest must be alphanumeric plus -, + and . + for (int i = 0; i < proto; i++) { + unsigned char c = path.start[i]; + if ((i == 0 && !mp_isalpha(c)) || + (!mp_isalnum(c) && c != '.' && c != '-' && c != '+')) + { + return false; + } + } + return true; +} + +// Return the protocol part of path, e.g. "http" if path is "http://...". +// On success, out_url (if not NULL) is set to the part after the "://". +bstr mp_split_proto(bstr path, bstr *out_url) +{ + if (!mp_is_url(path)) + return (bstr){0}; + bstr r; + bstr_split_tok(path, "://", &r, out_url ? out_url : &(bstr){0}); + return r; +} + +void mp_mkdirp(const char *dir) +{ + char *path = talloc_strdup(NULL, dir); + char *cdir = path + 1; + + while (cdir) { + cdir = strchr(cdir, '/'); + if (cdir) + *cdir = 0; + + mkdir(path, 0700); + + if (cdir) + *cdir++ = '/'; + } + + talloc_free(path); +} diff --git a/misc/path_utils.h b/misc/path_utils.h new file mode 100644 index 0000000..1e6c16a --- /dev/null +++ b/misc/path_utils.h @@ -0,0 +1,64 @@ +/* + * Path utility functions + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdbool.h> +#include "misc/bstr.h" + +// Return pointer to filename part of path + +char *mp_basename(const char *path); + +/* Return file extension, excluding the '.'. If root is not NULL, set it to the + * part of the path without extension. So: path == root + "." + extension + * Don't consider it a file extension if the only '.' is the first character. + * Return NULL if no extension and don't set *root in this case. + */ +char *mp_splitext(const char *path, bstr *root); + +/* Return struct bstr referencing directory part of path, or if that + * would be empty, ".". + */ +struct bstr mp_dirname(const char *path); + +void mp_path_strip_trailing_separator(char *path); + +/* Join two path components and return a newly allocated string + * for the result. '/' is inserted between the components if needed. + * If p2 is an absolute path then the value of p1 is ignored. + */ +char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2); +char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2); + +// Return whether the path is absolute. +bool mp_path_is_absolute(struct bstr path); + +char *mp_getcwd(void *talloc_ctx); + +char *mp_normalize_path(void *talloc_ctx, const char *path); + +bool mp_path_exists(const char *path); +bool mp_path_isdir(const char *path); + +bool mp_is_url(bstr path); + +bstr mp_split_proto(bstr path, bstr *out_url); + +void mp_mkdirp(const char *dir); diff --git a/misc/rendezvous.c b/misc/rendezvous.c index 1fe5724..cb1cde6 100644 --- a/misc/rendezvous.c +++ b/misc/rendezvous.c @@ -28,7 +28,7 @@ struct waiter { * of _all_ waiters in the process, and temporarily wakes up _all_ waiters on * each second call). * - * This is inspired by: http://9atom.org/magic/man2html/2/rendezvous */ + * This is inspired by: https://man.cat-v.org/plan_9/2/rendezvous */ intptr_t mp_rendezvous(void *tag, intptr_t value) { struct waiter wait = { .tag = tag, .value = &value }; |