55#include " htime.h"
66#include " hlog.h"
77
8- #include " httpdef.h" // import http_content_type_str_by_suffix
8+ #include " httpdef.h" // import http_content_type_str_by_suffix
99#include " http_page.h" // import make_index_of_page
1010
1111#ifdef OS_WIN
12- #include " hstring.h" // import hv::utf8_to_wchar
12+ #include " hstring.h" // import hv::utf8_to_wchar
1313#endif
1414
1515#define ETAG_FMT " \" %zx-%zx\" "
@@ -56,6 +56,7 @@ file_cache_ptr FileCache::Open(const char* filepath, OpenParam* param) {
5656 flags |= O_BINARY;
5757#endif
5858 int fd = -1 ;
59+ bool is_dir = false ;
5960#ifdef OS_WIN
6061 if (wfilepath.empty ()) wfilepath = hv::utf8_to_wchar (filepath);
6162 if (_wstat (wfilepath.c_str (), (struct _stat *)&st) != 0 ) {
@@ -65,7 +66,7 @@ file_cache_ptr FileCache::Open(const char* filepath, OpenParam* param) {
6566 if (S_ISREG (st.st_mode )) {
6667 fd = _wopen (wfilepath.c_str (), flags);
6768 } else if (S_ISDIR (st.st_mode )) {
68- fd = 0 ;
69+ is_dir = true ;
6970 }
7071#else
7172 if (::stat (filepath, &st) != 0 ) {
@@ -74,15 +75,11 @@ file_cache_ptr FileCache::Open(const char* filepath, OpenParam* param) {
7475 }
7576 fd = open (filepath, flags);
7677#endif
77- if (fd < 0 ) {
78+ if (fd < 0 && !is_dir ) {
7879 param->error = ERR_OPEN_FILE;
7980 return NULL ;
8081 }
81- #ifdef OS_WIN
82- defer (if (fd > 0 ) { close (fd); }) // fd=0 is Windows directory sentinel
83- #else
84- defer (close (fd);)
85- #endif
82+ defer (if (fd >= 0 ) { close (fd); })
8683 if (fc == NULL ) {
8784 if (S_ISREG (st.st_mode ) ||
8885 (S_ISDIR (st.st_mode ) &&
@@ -100,61 +97,67 @@ file_cache_ptr FileCache::Open(const char* filepath, OpenParam* param) {
10097 return NULL ;
10198 }
10299 }
103- // Hold fc->mutex for the remainder of initialization
104- std::lock_guard<std::mutex> lock (fc->mutex );
105- if (S_ISREG (fc->st .st_mode )) {
106- param->filesize = fc->st .st_size ;
107- // FILE
108- if (param->need_read ) {
109- if (fc->st .st_size > param->max_read ) {
110- param->error = ERR_OVER_LIMIT;
111- // Don't cache incomplete entries
112- return NULL ;
113- }
114- fc->resize_buf (fc->st .st_size , max_header_length);
115- // Loop to handle partial reads (EINTR, etc.)
116- char * dst = fc->filebuf .base ;
117- size_t remaining = fc->filebuf .len ;
118- while (remaining > 0 ) {
119- ssize_t nread = read (fd, dst, remaining);
120- if (nread < 0 ) {
121- if (errno == EINTR) continue ;
122- hloge (" Failed to read file: %s" , filepath);
123- param->error = ERR_READ_FILE;
100+ // Hold fc->mutex for initialization, but release before put()
101+ // to avoid lock-order inversion with RemoveExpiredFileCache().
102+ // Lock order: LRUCache mutex → fc->mutex (never reverse).
103+ {
104+ std::lock_guard<std::mutex> lock (fc->mutex );
105+ // Sync local stat result into cached entry
106+ fc->st = st;
107+ if (S_ISREG (fc->st .st_mode )) {
108+ param->filesize = fc->st .st_size ;
109+ // FILE
110+ if (param->need_read ) {
111+ if (fc->st .st_size > param->max_read ) {
112+ param->error = ERR_OVER_LIMIT;
113+ // Don't cache incomplete entries
124114 return NULL ;
125115 }
126- if (nread == 0 ) {
127- hloge (" Unexpected EOF reading file: %s" , filepath);
128- param->error = ERR_READ_FILE;
129- return NULL ;
116+ fc->resize_buf (fc->st .st_size , max_header_length);
117+ // Loop to handle partial reads (EINTR, etc.)
118+ char * dst = fc->filebuf .base ;
119+ size_t remaining = fc->filebuf .len ;
120+ while (remaining > 0 ) {
121+ ssize_t nread = read (fd, dst, remaining);
122+ if (nread < 0 ) {
123+ if (errno == EINTR) continue ;
124+ hloge (" Failed to read file: %s" , filepath);
125+ param->error = ERR_READ_FILE;
126+ return NULL ;
127+ }
128+ if (nread == 0 ) {
129+ hloge (" Unexpected EOF reading file: %s" , filepath);
130+ param->error = ERR_READ_FILE;
131+ return NULL ;
132+ }
133+ dst += nread;
134+ remaining -= nread;
130135 }
131- dst += nread;
132- remaining -= nread;
133136 }
134- }
135- const char * suffix = strrchr (filepath, ' . ' );
136- if (suffix) {
137- http_content_type content_type = http_content_type_enum_by_suffix (suffix + 1 );
138- if ( content_type == TEXT_HTML) {
139- fc-> content_type = " text/html; charset=utf-8 " ;
140- } else if ( content_type == TEXT_PLAIN) {
141- fc-> content_type = " text/plain; charset=utf-8 " ;
142- } else {
143- fc-> content_type = http_content_type_str_by_suffix (suffix + 1 );
137+ const char * suffix = strrchr (filepath, ' . ' );
138+ if (suffix) {
139+ http_content_type content_type = http_content_type_enum_by_suffix (suffix + 1 );
140+ if (content_type == TEXT_HTML) {
141+ fc-> content_type = " text/html; charset=utf-8 " ;
142+ } else if ( content_type == TEXT_PLAIN) {
143+ fc-> content_type = " text/plain; charset=utf-8 " ;
144+ } else {
145+ fc-> content_type = http_content_type_str_by_suffix (suffix + 1 );
146+ }
144147 }
148+ } else if (S_ISDIR (fc->st .st_mode )) {
149+ // DIR
150+ std::string page;
151+ make_index_of_page (filepath, page, param->path );
152+ fc->resize_buf (page.size (), max_header_length);
153+ memcpy (fc->filebuf .base , page.c_str (), page.size ());
154+ fc->content_type = " text/html; charset=utf-8" ;
145155 }
146- } else if (S_ISDIR (fc->st .st_mode )) {
147- // DIR
148- std::string page;
149- make_index_of_page (filepath, page, param->path );
150- fc->resize_buf (page.size (), max_header_length);
151- memcpy (fc->filebuf .base , page.c_str (), page.size ());
152- fc->content_type = " text/html; charset=utf-8" ;
153- }
154- gmtime_fmt (fc->st .st_mtime , fc->last_modified );
155- snprintf (fc->etag , sizeof (fc->etag ), ETAG_FMT,
156- (size_t )fc->st .st_mtime , (size_t )fc->st .st_size );
157- // Cache the fully initialized entry
156+ gmtime_fmt (fc->st .st_mtime , fc->last_modified );
157+ snprintf (fc->etag , sizeof (fc->etag ), ETAG_FMT,
158+ (size_t )fc->st .st_mtime , (size_t )fc->st .st_size );
159+ } // release fc->mutex before put() to maintain lock ordering
160+ // Cache the fully initialized entry (acquires LRUCache mutex only)
158161 put (filepath, fc);
159162 }
160163 return fc;
@@ -179,7 +182,12 @@ file_cache_ptr FileCache::Get(const char* filepath) {
179182void FileCache::RemoveExpiredFileCache () {
180183 time_t now = time (NULL );
181184 remove_if ([this , now](const std::string& filepath, const file_cache_ptr& fc) {
182- std::lock_guard<std::mutex> lock (fc->mutex );
185+ // Use try_to_lock to avoid lock-order inversion with Open().
186+ // If the entry is busy, skip it — it will be checked next cycle.
187+ std::unique_lock<std::mutex> lock (fc->mutex , std::try_to_lock);
188+ if (!lock.owns_lock ()) {
189+ return false ;
190+ }
183191 return (now - fc->stat_time > expired_time);
184192 });
185193}
0 commit comments