1d4afb5ceSopenharmony_ci/*
2d4afb5ceSopenharmony_ci * libwebsockets - small server side websockets and web server implementation
3d4afb5ceSopenharmony_ci *
4d4afb5ceSopenharmony_ci * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
5d4afb5ceSopenharmony_ci *
6d4afb5ceSopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a copy
7d4afb5ceSopenharmony_ci * of this software and associated documentation files (the "Software"), to
8d4afb5ceSopenharmony_ci * deal in the Software without restriction, including without limitation the
9d4afb5ceSopenharmony_ci * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10d4afb5ceSopenharmony_ci * sell copies of the Software, and to permit persons to whom the Software is
11d4afb5ceSopenharmony_ci * furnished to do so, subject to the following conditions:
12d4afb5ceSopenharmony_ci *
13d4afb5ceSopenharmony_ci * The above copyright notice and this permission notice shall be included in
14d4afb5ceSopenharmony_ci * all copies or substantial portions of the Software.
15d4afb5ceSopenharmony_ci *
16d4afb5ceSopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17d4afb5ceSopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18d4afb5ceSopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19d4afb5ceSopenharmony_ci * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20d4afb5ceSopenharmony_ci * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21d4afb5ceSopenharmony_ci * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22d4afb5ceSopenharmony_ci * IN THE SOFTWARE.
23d4afb5ceSopenharmony_ci */
24d4afb5ceSopenharmony_ci
25d4afb5ceSopenharmony_ci#if !defined(_GNU_SOURCE)
26d4afb5ceSopenharmony_ci#define _GNU_SOURCE
27d4afb5ceSopenharmony_ci#endif
28d4afb5ceSopenharmony_ci#include <pthread.h>
29d4afb5ceSopenharmony_ci
30d4afb5ceSopenharmony_ci#include <libwebsockets.h>
31d4afb5ceSopenharmony_ci#include "private-lib-core.h"
32d4afb5ceSopenharmony_ci
33d4afb5ceSopenharmony_ci#include <string.h>
34d4afb5ceSopenharmony_ci#include <stdio.h>
35d4afb5ceSopenharmony_ci#include <unistd.h>
36d4afb5ceSopenharmony_ci#include <fcntl.h>
37d4afb5ceSopenharmony_ci#include <dirent.h>
38d4afb5ceSopenharmony_ci#include <time.h>
39d4afb5ceSopenharmony_ci#include <errno.h>
40d4afb5ceSopenharmony_ci#include <stdarg.h>
41d4afb5ceSopenharmony_ci
42d4afb5ceSopenharmony_ci#include <sys/stat.h>
43d4afb5ceSopenharmony_ci#include <sys/time.h>
44d4afb5ceSopenharmony_ci#include <sys/types.h>
45d4afb5ceSopenharmony_ci
46d4afb5ceSopenharmony_ci#if defined(__APPLE__)
47d4afb5ceSopenharmony_ci#include <sys/dirent.h>
48d4afb5ceSopenharmony_ci/* Travis OSX does not have DT_REG... */
49d4afb5ceSopenharmony_ci#if !defined(DT_REG)
50d4afb5ceSopenharmony_ci#define DT_REG 8
51d4afb5ceSopenharmony_ci#endif
52d4afb5ceSopenharmony_ci#endif
53d4afb5ceSopenharmony_ci
54d4afb5ceSopenharmony_cistruct file_entry {
55d4afb5ceSopenharmony_ci	lws_list_ptr sorted;
56d4afb5ceSopenharmony_ci	lws_list_ptr prev;
57d4afb5ceSopenharmony_ci	char name[64];
58d4afb5ceSopenharmony_ci	time_t modified;
59d4afb5ceSopenharmony_ci	size_t size;
60d4afb5ceSopenharmony_ci};
61d4afb5ceSopenharmony_ci
62d4afb5ceSopenharmony_cistruct lws_diskcache_scan {
63d4afb5ceSopenharmony_ci	struct file_entry *batch;
64d4afb5ceSopenharmony_ci	const char *cache_dir_base;
65d4afb5ceSopenharmony_ci	lws_list_ptr head;
66d4afb5ceSopenharmony_ci	time_t last_scan_completed;
67d4afb5ceSopenharmony_ci	uint64_t agg_size;
68d4afb5ceSopenharmony_ci	uint64_t cache_size_limit;
69d4afb5ceSopenharmony_ci	uint64_t avg_size;
70d4afb5ceSopenharmony_ci	uint64_t cache_tries;
71d4afb5ceSopenharmony_ci	uint64_t cache_hits;
72d4afb5ceSopenharmony_ci	int cache_subdir;
73d4afb5ceSopenharmony_ci	int batch_in_use;
74d4afb5ceSopenharmony_ci	int agg_file_count;
75d4afb5ceSopenharmony_ci	int secs_waiting;
76d4afb5ceSopenharmony_ci};
77d4afb5ceSopenharmony_ci
78d4afb5ceSopenharmony_ci#define KIB (1024)
79d4afb5ceSopenharmony_ci#define MIB (KIB * KIB)
80d4afb5ceSopenharmony_ci
81d4afb5ceSopenharmony_ci#define lp_to_fe(p, _n) lws_list_ptr_container(p, struct file_entry, _n)
82d4afb5ceSopenharmony_ci
83d4afb5ceSopenharmony_cistatic const char *hex = "0123456789abcdef";
84d4afb5ceSopenharmony_ci
85d4afb5ceSopenharmony_ci#define BATCH_COUNT 128
86d4afb5ceSopenharmony_ci
87d4afb5ceSopenharmony_cistatic int
88d4afb5ceSopenharmony_cife_modified_sort(lws_list_ptr a, lws_list_ptr b)
89d4afb5ceSopenharmony_ci{
90d4afb5ceSopenharmony_ci	struct file_entry *p1 = lp_to_fe(a, sorted), *p2 = lp_to_fe(b, sorted);
91d4afb5ceSopenharmony_ci
92d4afb5ceSopenharmony_ci	return (int)((long)p2->modified - (long)p1->modified);
93d4afb5ceSopenharmony_ci}
94d4afb5ceSopenharmony_ci
95d4afb5ceSopenharmony_cistruct lws_diskcache_scan *
96d4afb5ceSopenharmony_cilws_diskcache_create(const char *cache_dir_base, uint64_t cache_size_limit)
97d4afb5ceSopenharmony_ci{
98d4afb5ceSopenharmony_ci	struct lws_diskcache_scan *lds = lws_malloc(sizeof(*lds), "cachescan");
99d4afb5ceSopenharmony_ci
100d4afb5ceSopenharmony_ci	if (!lds)
101d4afb5ceSopenharmony_ci		return NULL;
102d4afb5ceSopenharmony_ci
103d4afb5ceSopenharmony_ci	memset(lds, 0, sizeof(*lds));
104d4afb5ceSopenharmony_ci
105d4afb5ceSopenharmony_ci	lds->cache_dir_base = cache_dir_base;
106d4afb5ceSopenharmony_ci	lds->cache_size_limit = cache_size_limit;
107d4afb5ceSopenharmony_ci
108d4afb5ceSopenharmony_ci	return lds;
109d4afb5ceSopenharmony_ci}
110d4afb5ceSopenharmony_ci
111d4afb5ceSopenharmony_civoid
112d4afb5ceSopenharmony_cilws_diskcache_destroy(struct lws_diskcache_scan **lds)
113d4afb5ceSopenharmony_ci{
114d4afb5ceSopenharmony_ci	if ((*lds)->batch)
115d4afb5ceSopenharmony_ci		lws_free((*lds)->batch);
116d4afb5ceSopenharmony_ci	lws_free(*lds);
117d4afb5ceSopenharmony_ci	*lds = NULL;
118d4afb5ceSopenharmony_ci}
119d4afb5ceSopenharmony_ci
120d4afb5ceSopenharmony_ciint
121d4afb5ceSopenharmony_cilws_diskcache_prepare(const char *cache_base_dir, int mode, uid_t uid)
122d4afb5ceSopenharmony_ci{
123d4afb5ceSopenharmony_ci	char dir[256];
124d4afb5ceSopenharmony_ci	int n, m;
125d4afb5ceSopenharmony_ci
126d4afb5ceSopenharmony_ci	(void)mkdir(cache_base_dir, (unsigned short)mode);
127d4afb5ceSopenharmony_ci	if (chown(cache_base_dir, uid, (gid_t)-1))
128d4afb5ceSopenharmony_ci		lwsl_err("%s: %s: unable to chown %d\n", __func__,
129d4afb5ceSopenharmony_ci			 cache_base_dir, uid);
130d4afb5ceSopenharmony_ci
131d4afb5ceSopenharmony_ci	for (n = 0; n < 16; n++) {
132d4afb5ceSopenharmony_ci		lws_snprintf(dir, sizeof(dir), "%s/%c", cache_base_dir, hex[n]);
133d4afb5ceSopenharmony_ci		(void)mkdir(dir, (mode_t)mode);
134d4afb5ceSopenharmony_ci		if (chown(dir, uid, (uid_t)-1))
135d4afb5ceSopenharmony_ci			lwsl_err("%s: %s: unable to chown %d\n", __func__,
136d4afb5ceSopenharmony_ci						 dir, uid);
137d4afb5ceSopenharmony_ci		for (m = 0; m < 16; m++) {
138d4afb5ceSopenharmony_ci			lws_snprintf(dir, sizeof(dir), "%s/%c/%c",
139d4afb5ceSopenharmony_ci				     cache_base_dir, hex[n], hex[m]);
140d4afb5ceSopenharmony_ci			(void)mkdir(dir, (mode_t)mode);
141d4afb5ceSopenharmony_ci			if (chown(dir, uid, (uid_t)-1))
142d4afb5ceSopenharmony_ci				lwsl_err("%s: %s: unable to chown %d\n",
143d4afb5ceSopenharmony_ci					 __func__, dir, uid);
144d4afb5ceSopenharmony_ci		}
145d4afb5ceSopenharmony_ci	}
146d4afb5ceSopenharmony_ci
147d4afb5ceSopenharmony_ci	return 0;
148d4afb5ceSopenharmony_ci}
149d4afb5ceSopenharmony_ci
150d4afb5ceSopenharmony_ci/* copies and then truncates the incoming name, and renames the file at the
151d4afb5ceSopenharmony_ci * untruncated path to have the new truncated name */
152d4afb5ceSopenharmony_ci
153d4afb5ceSopenharmony_ciint
154d4afb5ceSopenharmony_cilws_diskcache_finalize_name(char *cache)
155d4afb5ceSopenharmony_ci{
156d4afb5ceSopenharmony_ci	char ren[256], *p;
157d4afb5ceSopenharmony_ci
158d4afb5ceSopenharmony_ci	strncpy(ren, cache, sizeof(ren) - 1);
159d4afb5ceSopenharmony_ci	ren[sizeof(ren) - 1] = '\0';
160d4afb5ceSopenharmony_ci	p = strchr(cache, '~');
161d4afb5ceSopenharmony_ci	if (p) {
162d4afb5ceSopenharmony_ci		*p = '\0';
163d4afb5ceSopenharmony_ci		if (rename(ren, cache)) {
164d4afb5ceSopenharmony_ci			lwsl_err("%s: problem renaming %s to %s\n", __func__,
165d4afb5ceSopenharmony_ci				 ren, cache);
166d4afb5ceSopenharmony_ci			return 1;
167d4afb5ceSopenharmony_ci		}
168d4afb5ceSopenharmony_ci
169d4afb5ceSopenharmony_ci		return 0;
170d4afb5ceSopenharmony_ci	}
171d4afb5ceSopenharmony_ci
172d4afb5ceSopenharmony_ci	return 1;
173d4afb5ceSopenharmony_ci}
174d4afb5ceSopenharmony_ci
175d4afb5ceSopenharmony_ciint
176d4afb5ceSopenharmony_cilws_diskcache_query(struct lws_diskcache_scan *lds, int is_bot,
177d4afb5ceSopenharmony_ci		    const char *hash_hex, int *_fd, char *cache, int cache_len,
178d4afb5ceSopenharmony_ci		    size_t *extant_cache_len)
179d4afb5ceSopenharmony_ci{
180d4afb5ceSopenharmony_ci	struct stat s;
181d4afb5ceSopenharmony_ci	int n;
182d4afb5ceSopenharmony_ci
183d4afb5ceSopenharmony_ci	/* caching is disabled? */
184d4afb5ceSopenharmony_ci	if (!lds->cache_dir_base)
185d4afb5ceSopenharmony_ci		return LWS_DISKCACHE_QUERY_NO_CACHE;
186d4afb5ceSopenharmony_ci
187d4afb5ceSopenharmony_ci	if (!is_bot)
188d4afb5ceSopenharmony_ci		lds->cache_tries++;
189d4afb5ceSopenharmony_ci
190d4afb5ceSopenharmony_ci	n = lws_snprintf(cache, (size_t)cache_len, "%s/%c/%c/%s", lds->cache_dir_base,
191d4afb5ceSopenharmony_ci			 hash_hex[0], hash_hex[1], hash_hex);
192d4afb5ceSopenharmony_ci
193d4afb5ceSopenharmony_ci	lwsl_info("%s: job cache %s\n", __func__, cache);
194d4afb5ceSopenharmony_ci
195d4afb5ceSopenharmony_ci	*_fd = open(cache, O_RDONLY);
196d4afb5ceSopenharmony_ci	if (*_fd >= 0) {
197d4afb5ceSopenharmony_ci		int fd;
198d4afb5ceSopenharmony_ci
199d4afb5ceSopenharmony_ci		if (!is_bot)
200d4afb5ceSopenharmony_ci			lds->cache_hits++;
201d4afb5ceSopenharmony_ci
202d4afb5ceSopenharmony_ci		if (fstat(*_fd, &s)) {
203d4afb5ceSopenharmony_ci			close(*_fd);
204d4afb5ceSopenharmony_ci
205d4afb5ceSopenharmony_ci			return LWS_DISKCACHE_QUERY_NO_CACHE;
206d4afb5ceSopenharmony_ci		}
207d4afb5ceSopenharmony_ci
208d4afb5ceSopenharmony_ci		*extant_cache_len = (size_t)s.st_size;
209d4afb5ceSopenharmony_ci
210d4afb5ceSopenharmony_ci		/* "touch" the hit cache file so it's last for LRU now */
211d4afb5ceSopenharmony_ci		fd = open(cache, O_RDWR);
212d4afb5ceSopenharmony_ci		if (fd >= 0)
213d4afb5ceSopenharmony_ci			close(fd);
214d4afb5ceSopenharmony_ci
215d4afb5ceSopenharmony_ci		return LWS_DISKCACHE_QUERY_EXISTS;
216d4afb5ceSopenharmony_ci	}
217d4afb5ceSopenharmony_ci
218d4afb5ceSopenharmony_ci	/* bots are too random to pollute the cache with their antics */
219d4afb5ceSopenharmony_ci	if (is_bot)
220d4afb5ceSopenharmony_ci		return LWS_DISKCACHE_QUERY_NO_CACHE;
221d4afb5ceSopenharmony_ci
222d4afb5ceSopenharmony_ci	/* let's create it first with a unique temp name */
223d4afb5ceSopenharmony_ci
224d4afb5ceSopenharmony_ci	lws_snprintf(cache + n, (size_t)cache_len - (unsigned int)n, "~%d-%p", (int)getpid(),
225d4afb5ceSopenharmony_ci		     extant_cache_len);
226d4afb5ceSopenharmony_ci
227d4afb5ceSopenharmony_ci	*_fd = open(cache, O_RDWR | O_CREAT | O_TRUNC, 0600);
228d4afb5ceSopenharmony_ci	if (*_fd < 0) {
229d4afb5ceSopenharmony_ci		/* well... ok... we will proceed without cache then... */
230d4afb5ceSopenharmony_ci		lwsl_notice("%s: Problem creating cache %s: errno %d\n",
231d4afb5ceSopenharmony_ci			    __func__, cache, errno);
232d4afb5ceSopenharmony_ci		return LWS_DISKCACHE_QUERY_NO_CACHE;
233d4afb5ceSopenharmony_ci	}
234d4afb5ceSopenharmony_ci
235d4afb5ceSopenharmony_ci	return LWS_DISKCACHE_QUERY_CREATING;
236d4afb5ceSopenharmony_ci}
237d4afb5ceSopenharmony_ci
238d4afb5ceSopenharmony_ciint
239d4afb5ceSopenharmony_cilws_diskcache_secs_to_idle(struct lws_diskcache_scan *lds)
240d4afb5ceSopenharmony_ci{
241d4afb5ceSopenharmony_ci	return lds->secs_waiting;
242d4afb5ceSopenharmony_ci}
243d4afb5ceSopenharmony_ci
244d4afb5ceSopenharmony_ci/*
245d4afb5ceSopenharmony_ci * The goal is to collect the oldest BATCH_COUNT filepaths and filesizes from
246d4afb5ceSopenharmony_ci * the dirs under the cache dir.  Since we don't need or want a full list of
247d4afb5ceSopenharmony_ci * files in there in memory at once, we restrict the linked-list size to
248d4afb5ceSopenharmony_ci * BATCH_COUNT entries, and once it is full, simply ignore any further files
249d4afb5ceSopenharmony_ci * that are newer than the newest one on that list.  Files older than the
250d4afb5ceSopenharmony_ci * newest guy already on the list evict the newest guy already on the list
251d4afb5ceSopenharmony_ci * and are sorted into the correct order.  In this way no matter the number
252d4afb5ceSopenharmony_ci * of files to be processed the memory requirement is fixed at BATCH_COUNT
253d4afb5ceSopenharmony_ci * struct file_entry-s.
254d4afb5ceSopenharmony_ci *
255d4afb5ceSopenharmony_ci * The oldest subset of BATCH_COUNT files are sorted into the cd->batch
256d4afb5ceSopenharmony_ci * allocation in more recent -> least recent order.
257d4afb5ceSopenharmony_ci *
258d4afb5ceSopenharmony_ci * We want to track the total size of all files we saw as well, so we know if
259d4afb5ceSopenharmony_ci * we need to actually do anything yet to restrict how much space it's taking
260d4afb5ceSopenharmony_ci * up.
261d4afb5ceSopenharmony_ci *
262d4afb5ceSopenharmony_ci * And we want to do those things statefully and incrementally instead of one
263d4afb5ceSopenharmony_ci * big atomic operation, since the user may want a huge cache, so we look in
264d4afb5ceSopenharmony_ci * one cache dir at a time and track state in the repodir struct.
265d4afb5ceSopenharmony_ci *
266d4afb5ceSopenharmony_ci * When we have seen everything, we add the doubly-linked prev pointers and then
267d4afb5ceSopenharmony_ci * if we are over the limit, start deleting up to BATCH_COUNT files working back
268d4afb5ceSopenharmony_ci * from the end.
269d4afb5ceSopenharmony_ci */
270d4afb5ceSopenharmony_ci
271d4afb5ceSopenharmony_ciint
272d4afb5ceSopenharmony_cilws_diskcache_trim(struct lws_diskcache_scan *lds)
273d4afb5ceSopenharmony_ci{
274d4afb5ceSopenharmony_ci	size_t cache_size_limit = (size_t)lds->cache_size_limit;
275d4afb5ceSopenharmony_ci	char dirpath[132], filepath[132 + 32];
276d4afb5ceSopenharmony_ci	lws_list_ptr lp, op = NULL;
277d4afb5ceSopenharmony_ci	int files_trimmed = 0;
278d4afb5ceSopenharmony_ci	struct file_entry *p;
279d4afb5ceSopenharmony_ci	int fd, n, ret = -1;
280d4afb5ceSopenharmony_ci	size_t trimmed = 0;
281d4afb5ceSopenharmony_ci	struct dirent *de;
282d4afb5ceSopenharmony_ci	struct stat s;
283d4afb5ceSopenharmony_ci	DIR *dir;
284d4afb5ceSopenharmony_ci
285d4afb5ceSopenharmony_ci	if (!lds->cache_subdir) {
286d4afb5ceSopenharmony_ci
287d4afb5ceSopenharmony_ci		if (lds->last_scan_completed + lds->secs_waiting > time(NULL))
288d4afb5ceSopenharmony_ci			return 0;
289d4afb5ceSopenharmony_ci
290d4afb5ceSopenharmony_ci		lds->batch = lws_malloc(sizeof(struct file_entry) *
291d4afb5ceSopenharmony_ci				BATCH_COUNT, "cache_trim");
292d4afb5ceSopenharmony_ci		if (!lds->batch) {
293d4afb5ceSopenharmony_ci			lwsl_err("%s: OOM\n", __func__);
294d4afb5ceSopenharmony_ci
295d4afb5ceSopenharmony_ci			return 1;
296d4afb5ceSopenharmony_ci		}
297d4afb5ceSopenharmony_ci		lds->agg_size = 0;
298d4afb5ceSopenharmony_ci		lds->head = NULL;
299d4afb5ceSopenharmony_ci		lds->batch_in_use = 0;
300d4afb5ceSopenharmony_ci		lds->agg_file_count = 0;
301d4afb5ceSopenharmony_ci	}
302d4afb5ceSopenharmony_ci
303d4afb5ceSopenharmony_ci	lws_snprintf(dirpath, sizeof(dirpath), "%s/%c/%c",
304d4afb5ceSopenharmony_ci		     lds->cache_dir_base, hex[(lds->cache_subdir >> 4) & 15],
305d4afb5ceSopenharmony_ci		     hex[lds->cache_subdir & 15]);
306d4afb5ceSopenharmony_ci
307d4afb5ceSopenharmony_ci	dir = opendir(dirpath);
308d4afb5ceSopenharmony_ci	if (!dir) {
309d4afb5ceSopenharmony_ci		lwsl_err("Unable to walk repo dir '%s'\n",
310d4afb5ceSopenharmony_ci			 lds->cache_dir_base);
311d4afb5ceSopenharmony_ci		return -1;
312d4afb5ceSopenharmony_ci	}
313d4afb5ceSopenharmony_ci
314d4afb5ceSopenharmony_ci	do {
315d4afb5ceSopenharmony_ci		de = readdir(dir);
316d4afb5ceSopenharmony_ci		if (!de)
317d4afb5ceSopenharmony_ci			break;
318d4afb5ceSopenharmony_ci
319d4afb5ceSopenharmony_ci		if (de->d_type != DT_REG)
320d4afb5ceSopenharmony_ci			continue;
321d4afb5ceSopenharmony_ci
322d4afb5ceSopenharmony_ci		lds->agg_file_count++;
323d4afb5ceSopenharmony_ci
324d4afb5ceSopenharmony_ci		lws_snprintf(filepath, sizeof(filepath), "%s/%s", dirpath,
325d4afb5ceSopenharmony_ci			     de->d_name);
326d4afb5ceSopenharmony_ci
327d4afb5ceSopenharmony_ci		fd = open(filepath, O_RDONLY);
328d4afb5ceSopenharmony_ci		if (fd < 0) {
329d4afb5ceSopenharmony_ci			lwsl_err("%s: cannot open %s\n", __func__, filepath);
330d4afb5ceSopenharmony_ci
331d4afb5ceSopenharmony_ci			continue;
332d4afb5ceSopenharmony_ci		}
333d4afb5ceSopenharmony_ci
334d4afb5ceSopenharmony_ci		n = fstat(fd, &s);
335d4afb5ceSopenharmony_ci		close(fd);
336d4afb5ceSopenharmony_ci		if (n) {
337d4afb5ceSopenharmony_ci			lwsl_notice("%s: cannot stat %s\n", __func__, filepath);
338d4afb5ceSopenharmony_ci			continue;
339d4afb5ceSopenharmony_ci		}
340d4afb5ceSopenharmony_ci
341d4afb5ceSopenharmony_ci		lds->agg_size += (uint64_t)s.st_size;
342d4afb5ceSopenharmony_ci
343d4afb5ceSopenharmony_ci		if (lds->batch_in_use == BATCH_COUNT) {
344d4afb5ceSopenharmony_ci			/*
345d4afb5ceSopenharmony_ci			 * once we filled up the batch with candidates, we don't
346d4afb5ceSopenharmony_ci			 * need to consider any files newer than the newest guy
347d4afb5ceSopenharmony_ci			 * on the list...
348d4afb5ceSopenharmony_ci			 */
349d4afb5ceSopenharmony_ci			if (lp_to_fe(lds->head, sorted)->modified < s.st_mtime)
350d4afb5ceSopenharmony_ci				continue;
351d4afb5ceSopenharmony_ci
352d4afb5ceSopenharmony_ci			/*
353d4afb5ceSopenharmony_ci			 * ... and if we find an older file later, we know it
354d4afb5ceSopenharmony_ci			 * will be replacing the newest guy on the list, so use
355d4afb5ceSopenharmony_ci			 * that directly...
356d4afb5ceSopenharmony_ci			 */
357d4afb5ceSopenharmony_ci			p = lds->head;
358d4afb5ceSopenharmony_ci			lds->head = p->sorted;
359d4afb5ceSopenharmony_ci		} else
360d4afb5ceSopenharmony_ci			/* we are still accepting anything to fill the batch */
361d4afb5ceSopenharmony_ci
362d4afb5ceSopenharmony_ci			p = &lds->batch[lds->batch_in_use++];
363d4afb5ceSopenharmony_ci
364d4afb5ceSopenharmony_ci		p->sorted = NULL;
365d4afb5ceSopenharmony_ci		strncpy(p->name, de->d_name, sizeof(p->name) - 1);
366d4afb5ceSopenharmony_ci		p->name[sizeof(p->name) - 1] = '\0';
367d4afb5ceSopenharmony_ci		p->modified = s.st_mtime;
368d4afb5ceSopenharmony_ci		p->size = (size_t)s.st_size;
369d4afb5ceSopenharmony_ci
370d4afb5ceSopenharmony_ci		lws_list_ptr_insert(&lds->head, &p->sorted, fe_modified_sort);
371d4afb5ceSopenharmony_ci	} while (de);
372d4afb5ceSopenharmony_ci
373d4afb5ceSopenharmony_ci	ret = 0;
374d4afb5ceSopenharmony_ci
375d4afb5ceSopenharmony_ci	lds->cache_subdir++;
376d4afb5ceSopenharmony_ci	if (lds->cache_subdir != 0x100)
377d4afb5ceSopenharmony_ci		goto done;
378d4afb5ceSopenharmony_ci
379d4afb5ceSopenharmony_ci	/* we completed the whole scan... */
380d4afb5ceSopenharmony_ci
381d4afb5ceSopenharmony_ci	/* if really no guidence, then 256MiB */
382d4afb5ceSopenharmony_ci	if (!cache_size_limit)
383d4afb5ceSopenharmony_ci		cache_size_limit = 256 * 1024 * 1024;
384d4afb5ceSopenharmony_ci
385d4afb5ceSopenharmony_ci	if (lds->agg_size > cache_size_limit) {
386d4afb5ceSopenharmony_ci
387d4afb5ceSopenharmony_ci		/* apply prev pointers to make the list doubly-linked */
388d4afb5ceSopenharmony_ci
389d4afb5ceSopenharmony_ci		lp = lds->head;
390d4afb5ceSopenharmony_ci		while (lp) {
391d4afb5ceSopenharmony_ci			p = lp_to_fe(lp, sorted);
392d4afb5ceSopenharmony_ci
393d4afb5ceSopenharmony_ci			p->prev = op;
394d4afb5ceSopenharmony_ci			op = &p->prev;
395d4afb5ceSopenharmony_ci			lp = p->sorted;
396d4afb5ceSopenharmony_ci		}
397d4afb5ceSopenharmony_ci
398d4afb5ceSopenharmony_ci		/*
399d4afb5ceSopenharmony_ci		 * reverse the list (start from tail, now traverse using
400d4afb5ceSopenharmony_ci		 * .prev)... it's oldest-first now...
401d4afb5ceSopenharmony_ci		 */
402d4afb5ceSopenharmony_ci
403d4afb5ceSopenharmony_ci		lp = op;
404d4afb5ceSopenharmony_ci
405d4afb5ceSopenharmony_ci		while (lp && lds->agg_size > cache_size_limit) {
406d4afb5ceSopenharmony_ci			p = lp_to_fe(lp, prev);
407d4afb5ceSopenharmony_ci
408d4afb5ceSopenharmony_ci			lws_snprintf(filepath, sizeof(filepath), "%s/%c/%c/%s",
409d4afb5ceSopenharmony_ci				     lds->cache_dir_base, p->name[0],
410d4afb5ceSopenharmony_ci				     p->name[1], p->name);
411d4afb5ceSopenharmony_ci
412d4afb5ceSopenharmony_ci			if (!unlink(filepath)) {
413d4afb5ceSopenharmony_ci				lds->agg_size -= p->size;
414d4afb5ceSopenharmony_ci				trimmed += p->size;
415d4afb5ceSopenharmony_ci				files_trimmed++;
416d4afb5ceSopenharmony_ci			} else
417d4afb5ceSopenharmony_ci				lwsl_notice("%s: Failed to unlink %s\n",
418d4afb5ceSopenharmony_ci					    __func__, filepath);
419d4afb5ceSopenharmony_ci
420d4afb5ceSopenharmony_ci			lp = p->prev;
421d4afb5ceSopenharmony_ci		}
422d4afb5ceSopenharmony_ci
423d4afb5ceSopenharmony_ci		if (files_trimmed)
424d4afb5ceSopenharmony_ci			lwsl_notice("%s: %s: trimmed %d files totalling "
425d4afb5ceSopenharmony_ci				    "%lldKib, leaving %lldMiB\n", __func__,
426d4afb5ceSopenharmony_ci				    lds->cache_dir_base, files_trimmed,
427d4afb5ceSopenharmony_ci				    ((unsigned long long)trimmed) / KIB,
428d4afb5ceSopenharmony_ci				    ((unsigned long long)lds->agg_size) / MIB);
429d4afb5ceSopenharmony_ci	}
430d4afb5ceSopenharmony_ci
431d4afb5ceSopenharmony_ci	if (lds->agg_size && lds->agg_file_count)
432d4afb5ceSopenharmony_ci		lds->avg_size = lds->agg_size / (uint64_t)lds->agg_file_count;
433d4afb5ceSopenharmony_ci
434d4afb5ceSopenharmony_ci	/*
435d4afb5ceSopenharmony_ci	 * estimate how long we can go before scanning again... default we need
436d4afb5ceSopenharmony_ci	 * to start again immediately
437d4afb5ceSopenharmony_ci	 */
438d4afb5ceSopenharmony_ci
439d4afb5ceSopenharmony_ci	lds->last_scan_completed = time(NULL);
440d4afb5ceSopenharmony_ci	lds->secs_waiting = 1;
441d4afb5ceSopenharmony_ci
442d4afb5ceSopenharmony_ci	if (lds->agg_size < cache_size_limit) {
443d4afb5ceSopenharmony_ci		uint64_t avg = 4096, capacity, projected;
444d4afb5ceSopenharmony_ci
445d4afb5ceSopenharmony_ci		/* let's use 80% of the real average for margin */
446d4afb5ceSopenharmony_ci		if (lds->agg_size && lds->agg_file_count)
447d4afb5ceSopenharmony_ci			avg = ((lds->agg_size * 8) / (uint64_t)lds->agg_file_count) / 10;
448d4afb5ceSopenharmony_ci
449d4afb5ceSopenharmony_ci		/*
450d4afb5ceSopenharmony_ci		 * if we collected BATCH_COUNT files of the average size,
451d4afb5ceSopenharmony_ci		 * how much can we clean up in 256s?
452d4afb5ceSopenharmony_ci		 */
453d4afb5ceSopenharmony_ci
454d4afb5ceSopenharmony_ci		capacity = avg * BATCH_COUNT;
455d4afb5ceSopenharmony_ci
456d4afb5ceSopenharmony_ci		/*
457d4afb5ceSopenharmony_ci		 * if the cache grew by 10%, would we hit the limit even then?
458d4afb5ceSopenharmony_ci		 */
459d4afb5ceSopenharmony_ci		projected = (lds->agg_size * 11) / 10;
460d4afb5ceSopenharmony_ci		if (projected < cache_size_limit)
461d4afb5ceSopenharmony_ci			/* no... */
462d4afb5ceSopenharmony_ci			lds->secs_waiting  = (int)((256 / 2) * ((cache_size_limit -
463d4afb5ceSopenharmony_ci						    projected) / capacity));
464d4afb5ceSopenharmony_ci
465d4afb5ceSopenharmony_ci		/*
466d4afb5ceSopenharmony_ci		 * large waits imply we may not have enough info yet, so
467d4afb5ceSopenharmony_ci		 * check once an hour at least.
468d4afb5ceSopenharmony_ci		 */
469d4afb5ceSopenharmony_ci
470d4afb5ceSopenharmony_ci		if (lds->secs_waiting > 3600)
471d4afb5ceSopenharmony_ci			lds->secs_waiting = 3600;
472d4afb5ceSopenharmony_ci	} else
473d4afb5ceSopenharmony_ci		lds->secs_waiting = 0;
474d4afb5ceSopenharmony_ci
475d4afb5ceSopenharmony_ci	lwsl_info("%s: cache %s: %lldKiB / %lldKiB, next scan %ds\n", __func__,
476d4afb5ceSopenharmony_ci		  lds->cache_dir_base,
477d4afb5ceSopenharmony_ci		  (unsigned long long)lds->agg_size / KIB,
478d4afb5ceSopenharmony_ci		  (unsigned long long)cache_size_limit / KIB,
479d4afb5ceSopenharmony_ci		  lds->secs_waiting);
480d4afb5ceSopenharmony_ci
481d4afb5ceSopenharmony_ci	lws_free(lds->batch);
482d4afb5ceSopenharmony_ci	lds->batch = NULL;
483d4afb5ceSopenharmony_ci
484d4afb5ceSopenharmony_ci	lds->cache_subdir = 0;
485d4afb5ceSopenharmony_ci
486d4afb5ceSopenharmony_cidone:
487d4afb5ceSopenharmony_ci	closedir(dir);
488d4afb5ceSopenharmony_ci
489d4afb5ceSopenharmony_ci	return ret;
490d4afb5ceSopenharmony_ci}
491