+
+BT_HIDDEN
+void bt_common_normalize_star_glob_pattern(char *pattern)
+{
+ const char *p;
+ char *np;
+ bool got_star = false;
+
+ assert(pattern);
+
+ for (p = pattern, np = pattern; *p != '\0'; p++) {
+ switch (*p) {
+ case '*':
+ if (got_star) {
+ /* Avoid consecutive stars. */
+ continue;
+ }
+
+ got_star = true;
+ break;
+ case '\\':
+ /* Copy backslash character. */
+ *np = *p;
+ np++;
+ p++;
+
+ if (*p == '\0') {
+ goto end;
+ }
+
+ /* Fall through default case. */
+ default:
+ got_star = false;
+ break;
+ }
+
+ /* Copy single character. */
+ *np = *p;
+ np++;
+ }
+
+end:
+ *np = '\0';
+}
+
+static inline
+bool at_end_of_pattern(const char *p, const char *pattern, size_t pattern_len)
+{
+ return (p - pattern) == pattern_len || *p == '\0';
+}
+
+/*
+ * Globbing matching function with the star feature only (`?` and
+ * character sets are not supported). This matches `candidate` (plain
+ * string) against `pattern`. A literal star can be escaped with `\` in
+ * `pattern`.
+ *
+ * `pattern_len` or `candidate_len` can be greater than the actual
+ * string length of `pattern` or `candidate` if the string is
+ * null-terminated.
+ */
+BT_HIDDEN
+bool bt_common_star_glob_match(const char *pattern, size_t pattern_len,
+ const char *candidate, size_t candidate_len) {
+ const char *retry_c = candidate, *retry_p = pattern, *c, *p;
+ bool got_a_star = false;
+
+retry:
+ c = retry_c;
+ p = retry_p;
+
+ /*
+ * The concept here is to retry a match in the specific case
+ * where we already got a star. The retry position for the
+ * pattern is just after the most recent star, and the retry
+ * position for the candidate is the character following the
+ * last try's first character.
+ *
+ * Example:
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^ SUCCESS
+ */
+ while ((c - candidate) < candidate_len && *c != '\0') {
+ assert(*c);
+
+ if (at_end_of_pattern(p, pattern, pattern_len)) {
+ goto end_of_pattern;
+ }
+
+ switch (*p) {
+ case '*':
+ got_a_star = true;
+
+ /*
+ * Our first try starts at the current candidate
+ * character and after the star in the pattern.
+ */
+ retry_c = c;
+ retry_p = p + 1;
+
+ if (at_end_of_pattern(retry_p, pattern, pattern_len)) {
+ /*
+ * Star at the end of the pattern at
+ * this point: automatic match.
+ */
+ return true;
+ }
+
+ goto retry;
+ case '\\':
+ /* Go to escaped character. */
+ p++;
+
+ /*
+ * Fall through the default case which compares
+ * the escaped character now.
+ */
+ default:
+ if (at_end_of_pattern(p, pattern, pattern_len) ||
+ *c != *p) {
+end_of_pattern:
+ /* Character mismatch OR end of pattern. */
+ if (!got_a_star) {
+ /*
+ * We didn't get any star yet,
+ * so this first mismatch
+ * automatically makes the whole
+ * test fail.
+ */
+ return false;
+ }
+
+ /*
+ * Next try: next candidate character,
+ * original pattern character (following
+ * the most recent star).
+ */
+ retry_c++;
+ goto retry;
+ }
+ break;
+ }
+
+ /* Next pattern and candidate characters. */
+ c++;
+ p++;
+ }
+
+ /*
+ * We checked every candidate character and we're still in a
+ * success state: the only pattern character allowed to remain
+ * is a star.
+ */
+ if (at_end_of_pattern(p, pattern, pattern_len)) {
+ return true;
+ }
+
+ p++;
+ return p[-1] == '*' && at_end_of_pattern(p, pattern, pattern_len);
+}
+
+static
+void append_path_parts(const char *path, GPtrArray *parts)
+{
+ const char *ch = path;
+ const char *last = path;
+
+ while (true) {
+ if (*ch == G_DIR_SEPARATOR || *ch == '\0') {
+ if (ch - last > 0) {
+ GString *part = g_string_new(NULL);
+
+ assert(part);
+ g_string_append_len(part, last, ch - last);
+ g_ptr_array_add(parts, part);
+ }
+
+ if (*ch == '\0') {
+ break;
+ }
+
+ last = ch + 1;
+ }
+
+ ch++;
+ }
+}
+
+static
+void destroy_gstring(void *gstring)
+{
+ (void) g_string_free(gstring, TRUE);
+}
+
+BT_HIDDEN
+GString *bt_common_normalize_path(const char *path, const char *wd)
+{
+ size_t i;
+ GString *norm_path;
+ GPtrArray *parts = NULL;
+
+ assert(path);
+ norm_path = g_string_new(G_DIR_SEPARATOR_S);
+ if (!norm_path) {
+ goto error;
+ }
+
+ parts = g_ptr_array_new_with_free_func(destroy_gstring);
+ if (!parts) {
+ goto error;
+ }
+
+ if (path[0] != G_DIR_SEPARATOR) {
+ /* Relative path: start with working directory */
+ if (wd) {
+ append_path_parts(wd, parts);
+ } else {
+ gchar *cd = g_get_current_dir();
+
+ append_path_parts(cd, parts);
+ g_free(cd);
+ }
+ }
+
+ /* Append parts of the path parameter */
+ append_path_parts(path, parts);
+
+ /* Resolve special `..` and `.` parts */
+ for (i = 0; i < parts->len; i++) {
+ GString *part = g_ptr_array_index(parts, i);
+
+ if (strcmp(part->str, "..") == 0) {
+ if (i == 0) {
+ /*
+ * First part of absolute path is `..`:
+ * this is invalid.
+ */
+ goto error;
+ }
+
+ /* Remove `..` and previous part */
+ g_ptr_array_remove_index(parts, i - 1);
+ g_ptr_array_remove_index(parts, i - 1);
+ i -= 2;
+ } else if (strcmp(part->str, ".") == 0) {
+ /* Remove `.` */
+ g_ptr_array_remove_index(parts, i);
+ i -= 1;
+ }
+ }
+
+ /* Create normalized path with what's left */
+ for (i = 0; i < parts->len; i++) {
+ GString *part = g_ptr_array_index(parts, i);
+
+ g_string_append(norm_path, part->str);
+
+ if (i < parts->len - 1) {
+ g_string_append_c(norm_path, G_DIR_SEPARATOR);
+ }
+ }
+
+ goto end;
+
+error:
+ if (norm_path) {
+ g_string_free(norm_path, TRUE);
+ norm_path = NULL;
+ }
+
+end:
+ if (parts) {
+ g_ptr_array_free(parts, TRUE);
+ }
+
+ return norm_path;
+}
+
+BT_HIDDEN
+size_t bt_common_get_page_size(void)
+{
+ int page_size;
+
+ page_size = bt_sysconf(_SC_PAGESIZE);
+ if (page_size < 0) {
+ printf_error("Cannot get system page size.");
+ abort();
+ }
+
+ return page_size;
+}