1 /*
2   +----------------------------------------------------------------------+
3   | Swoole                                                               |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 2.0 of the Apache license,    |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | http://www.apache.org/licenses/LICENSE-2.0.html                      |
9   | If you did not receive a copy of the Apache2.0 license and are unable|
10   | to obtain it through the world-wide-web, please send a note to       |
11   | license@swoole.com so we can mail you a copy immediately.            |
12   +----------------------------------------------------------------------+
13   | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
14   +----------------------------------------------------------------------+
15 */
16 
17 #include <sys/file.h>
18 
19 #include <queue>
20 
21 #include "swoole_coroutine.h"
22 #include "swoole_coroutine_c_api.h"
23 
24 using swoole::Coroutine;
25 
26 class LockManager {
27   public:
28     bool lock_ex = false;
29     bool lock_sh = false;
30     std::queue<Coroutine *> queue_;
31 };
32 
33 static std::unordered_map<std::string, LockManager *> lock_pool;
34 
get_manager(const char * filename)35 static inline LockManager *get_manager(const char *filename) {
36     std::string key(filename);
37     auto i = lock_pool.find(key);
38     LockManager *lm;
39     if (i == lock_pool.end()) {
40         lm = new LockManager;
41         lock_pool[key] = lm;
42     } else {
43         lm = i->second;
44     }
45     return lm;
46 }
47 
lock_ex(const char * filename,int fd)48 static inline int lock_ex(const char *filename, int fd) {
49     LockManager *lm = get_manager(filename);
50     if (lm->lock_ex || lm->lock_sh) {
51         Coroutine *co = Coroutine::get_current();
52         lm->queue_.push(co);
53         co->yield();
54     }
55     lm->lock_ex = true;
56     if (swoole_coroutine_flock(fd, LOCK_EX) < 0) {
57         lm->lock_ex = false;
58         return -1;
59     } else {
60         return 0;
61     }
62 }
63 
lock_sh(const char * filename,int fd)64 static inline int lock_sh(const char *filename, int fd) {
65     LockManager *lm = get_manager(filename);
66     if (lm->lock_ex) {
67         Coroutine *co = Coroutine::get_current();
68         lm->queue_.push(co);
69         co->yield();
70     }
71     lm->lock_sh = true;
72     if (swoole_coroutine_flock(fd, LOCK_SH) < 0) {
73         lm->lock_sh = false;
74         return -1;
75     } else {
76         return 0;
77     }
78 }
79 
lock_release(const char * filename,int fd)80 static inline int lock_release(const char *filename, int fd) {
81     std::string key(filename);
82     auto i = lock_pool.find(key);
83     if (i == lock_pool.end()) {
84         return swoole_coroutine_flock(fd, LOCK_UN);
85     }
86     LockManager *lm = i->second;
87     if (lm->queue_.empty()) {
88         delete lm;
89         lock_pool.erase(i);
90         return swoole_coroutine_flock(fd, LOCK_UN);
91     } else {
92         Coroutine *co = lm->queue_.front();
93         lm->queue_.pop();
94         int retval = swoole_coroutine_flock(fd, LOCK_UN);
95         co->resume();
96         return retval;
97     }
98 }
99 
100 #ifdef LOCK_NB
lock_nb(const char * filename,int fd,int operation)101 static inline int lock_nb(const char *filename, int fd, int operation) {
102     int retval = ::flock(fd, operation | LOCK_NB);
103     if (retval == 0) {
104         LockManager *lm = get_manager(filename);
105         if (operation == LOCK_EX) {
106             lm->lock_ex = true;
107         } else {
108             lm->lock_sh = true;
109         }
110     }
111     return retval;
112 }
113 #endif
114 
swoole_coroutine_flock_ex(const char * filename,int fd,int operation)115 int swoole_coroutine_flock_ex(const char *filename, int fd, int operation) {
116     Coroutine *co = Coroutine::get_current();
117     if (sw_unlikely(SwooleTG.reactor == nullptr || !co)) {
118         return ::flock(fd, operation);
119     }
120 
121     const char *real = realpath(filename, sw_tg_buffer()->str);
122     if (real == nullptr) {
123         errno = ENOENT;
124         swoole_set_last_error(ENOENT);
125         return -1;
126     }
127 
128     switch (operation) {
129     case LOCK_EX:
130         return lock_ex(real, fd);
131     case LOCK_SH:
132         return lock_sh(real, fd);
133     case LOCK_UN:
134         return lock_release(real, fd);
135     default:
136 #ifdef LOCK_NB
137         if (operation & LOCK_NB) {
138             return lock_nb(real, fd, operation & (~LOCK_NB));
139         }
140 #endif
141         return -1;
142     }
143 
144     return 0;
145 }
146