1#include <stdbool.h>
2#include <stdint.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include <dirent.h>
8#include <sys/stat.h>
9#include <unistd.h>
10
11#include "config.h"
12#include "scan.h"
13#include "log.h"
14#include "utils.h"
15
16static uint64_t encode_fnv1a64(const char* s);
17static int library_push(struct library* l, const char* rel);
18static int scan_dir(struct library* l, const char* root, const char* rel);
19
20/* Encode string using FNV1a64
21 Returns a 64bit hash value */
22static uint64_t encode_fnv1a64(const char* s)
23{
24 /* standard fnv1a64 encoding */
25 uint64_t h = 1469598103934665603ULL;
26
27 for (; *s; s++) {
28 h ^= (unsigned char)(*s);
29 h *= 1099511628211ULL;
30 }
31
32 return h;
33}
34
35/* Append item path to library
36 Returns 0=Success, -1=Failure */
37static int library_push(struct library* l, const char* rel)
38{
39 if (l->len == l->cap) {
40 size_t ncap = l->cap ? (l->cap * 2) : 64;
41 struct item* ni = realloc(l->items, ncap * sizeof(*ni));
42 if (!ni) {
43 LOG_FIELD(verbose_log, "SCAN", "realloc FAILED", "OOM");
44 return -1;
45 }
46 l->items = ni;
47 l->cap = ncap;
48 }
49
50 char* p = strdup(rel);
51 if (!p)
52 return -1;
53
54 l->items[l->len].id = encode_fnv1a64(rel);
55 l->items[l->len].path = p;
56 l->len++;
57
58 return 0;
59}
60
61/* Recursively scan one directory subtree
62 Returns 0=Success, -1=Failure */
63static int scan_dir(struct library* l, const char* root, const char* rel)
64{
65 char full[4096];
66 DIR* d;
67 struct dirent* e;
68
69 if (rel[0] == '\0') {
70 if (snprintf(full, sizeof(full), "%s", root) >= (int)sizeof(full))
71 return -1;
72 }
73 else {
74 if (join_path(full, sizeof(full), root, rel) < 0)
75 return -1;
76 }
77
78 d = opendir(full);
79 if (!d) {
80 LOG_FIELD(verbose_log, "SCAN", "opendir FAILED", "%s", full);
81 return -1;
82 }
83
84 while ((e = readdir(d)) != NULL) {
85 if (strcmp(e->d_name, ".") == 0)
86 continue;
87 if (strcmp(e->d_name, "..") == 0)
88 continue;
89
90 char rel2[4096];
91 char full2[4096];
92
93 if (rel[0] == '\0') {
94 if (snprintf(rel2, sizeof(rel2), "%s", e->d_name) >= (int)sizeof(rel2))
95 goto fail;
96 }
97 else {
98 if (join_path(rel2, sizeof(rel2), rel, e->d_name) < 0)
99 goto fail;
100 }
101
102 if (join_path(full2, sizeof(full2), root, rel2) < 0)
103 goto fail;
104
105 struct stat st;
106 if (lstat(full2, &st) < 0)
107 continue;
108
109 if (S_ISDIR(st.st_mode)) {
110 if (scan_dir(l, root, rel2) < 0)
111 goto fail;
112 continue;
113 }
114
115 if (S_ISREG(st.st_mode)) {
116 if (library_push(l, rel2) < 0)
117 goto fail;
118 continue;
119 }
120 }
121
122 closedir(d);
123 return 0;
124
125fail:
126 closedir(d);
127 return -1;
128}
129
130int scan_library(struct library* l, const char* root)
131{
132 memset(l, 0, sizeof(*l));
133 return scan_dir(l, root, "");
134}
135
136void scan_library_free(struct library* l)
137{
138 if (!l)
139 return;
140
141 for (size_t i = 0; i < l->len; i++)
142 free(l->items[i].path);
143
144 free(l->items);
145
146 l->items = NULL;
147 l->len = 0;
148 l->cap = 0;
149}
150
151int scan_library_rescan(struct library* l, const char* root)
152{
153 struct library new;
154 struct library old;
155
156 if (!l || !root)
157 return -1;
158
159 memset(&new, 0, sizeof(new));
160
161 if (scan_library(&new, root) < 0) {
162 scan_library_free(&new);
163 return -1;
164 }
165
166 /* swap */
167 old = *l;
168 *l = new;
169
170 /* free old */
171 scan_library_free(&old);
172
173 return 0;
174}
175