1 /*
2     Copyright (C) 2009-2016 Red Hat, Inc.
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 
17 */
18 #include <config.h>
19 
20 #include "red-channel.h"
21 #include "red-client.h"
22 #include "reds.h"
23 
24 #define FOREACH_CHANNEL_CLIENT(_client, _data) \
25     for (const auto &_data: _client->channels)
26 
~RedClient()27 RedClient::~RedClient()
28 {
29     spice_debug("release client=%p", this);
30     pthread_mutex_destroy(&lock);
31 }
32 
RedClient(RedsState * init_reds,bool migrated)33 RedClient::RedClient(RedsState *init_reds, bool migrated):
34     reds(init_reds),
35     during_target_migrate(migrated)
36 {
37     pthread_mutex_init(&lock, nullptr);
38     thread_id = pthread_self();
39 }
40 
red_client_new(RedsState * reds,int migrated)41 RedClient *red_client_new(RedsState *reds, int migrated)
42 {
43     return new RedClient(reds, migrated);
44 }
45 
set_migration_seamless()46 void RedClient::set_migration_seamless() // dest
47 {
48     spice_assert(during_target_migrate);
49     pthread_mutex_lock(&lock);
50     seamless_migrate = TRUE;
51     /* update channel clients that got connected before the migration
52      * type was set. red_client_add_channel will handle newer channel clients */
53     FOREACH_CHANNEL_CLIENT(this, rcc) {
54         if (rcc->set_migration_seamless()) {
55             num_migrated_channels++;
56         }
57     }
58     pthread_mutex_unlock(&lock);
59 }
60 
migrate()61 void RedClient::migrate()
62 {
63     if (!pthread_equal(pthread_self(), thread_id)) {
64         spice_warning("client->thread_id (%p) != "
65                       "pthread_self (%p)."
66                       "If one of the threads is != io-thread && != vcpu-thread,"
67                       " this might be a BUG",
68                       (void*) thread_id, (void*) pthread_self());
69     }
70     FOREACH_CHANNEL_CLIENT(this, rcc) {
71         if (rcc->is_connected()) {
72             auto channel = rcc->get_channel();
73             channel->migrate_client(rcc.get());
74         }
75     }
76 }
77 
destroy()78 void RedClient::destroy()
79 {
80     if (!pthread_equal(pthread_self(), thread_id)) {
81         spice_warning("client->thread_id (%p) != "
82                       "pthread_self (%p)."
83                       "If one of the threads is != io-thread && != vcpu-thread,"
84                       " this might be a BUG",
85                       (void*) thread_id,
86                       (void*) pthread_self());
87     }
88 
89     pthread_mutex_lock(&lock);
90     spice_debug("destroy this %p with #channels=%zd", this, channels.size());
91     // This makes sure that we won't try to add new RedChannelClient instances
92     // to the RedClient::channels list while iterating it
93     disconnecting = TRUE;
94     while (!channels.empty()) {
95         auto rcc = *channels.begin();
96 
97         // Remove the RedChannelClient we are processing from the list
98         // Note that we own the object so it is safe to do some operations on it.
99         // This manual scan of the list is done to have a thread safe
100         // iteration of the list
101         channels.pop_front();
102 
103         // prevent dead lock disconnecting rcc (which can happen
104         // in the same thread or synchronously on another one)
105         pthread_mutex_unlock(&lock);
106 
107         // some channels may be in other threads, so disconnection
108         // is not synchronous.
109         auto channel = rcc->get_channel();
110 
111         // some channels may be in other threads. However we currently
112         // assume disconnect is synchronous (we changed the dispatcher
113         // to wait for disconnection)
114         // TODO: should we go back to async. For this we need to use
115         // ref count for channel clients.
116         channel->disconnect_client(rcc.get());
117 
118         spice_assert(rcc->pipe_is_empty());
119         spice_assert(rcc->no_item_being_sent());
120 
121         pthread_mutex_lock(&lock);
122     }
123     pthread_mutex_unlock(&lock);
124     unref();
125 }
126 
127 
128 /* client->lock should be locked */
get_channel(int type,int id)129 RedChannelClient *RedClient::get_channel(int type, int id)
130 {
131     FOREACH_CHANNEL_CLIENT(this, rcc) {
132         RedChannel *channel;
133 
134         channel = rcc->get_channel();
135         if (channel->type() == type && channel->id() == id) {
136             return rcc.get();
137         }
138     }
139     return nullptr;
140 }
141 
add_channel(RedChannelClient * rcc,char ** error)142 gboolean RedClient::add_channel(RedChannelClient *rcc, char **error)
143 {
144     RedChannel *channel;
145     gboolean result = TRUE;
146 
147     spice_assert(rcc);
148     channel = rcc->get_channel();
149 
150     pthread_mutex_lock(&lock);
151 
152     uint32_t type = channel->type();
153     uint32_t id = channel->id();
154     if (disconnecting) {
155         *error =
156             g_strdup_printf("Client %p got disconnected while connecting channel type %d id %d",
157                             this, type, id);
158         result = FALSE;
159         goto cleanup;
160     }
161 
162     if (get_channel(type, id)) {
163         *error =
164             g_strdup_printf("Client %p: duplicate channel type %d id %d",
165                             this, type, id);
166         result = FALSE;
167         goto cleanup;
168     }
169 
170     // first must be the main one
171     if (!mcc) {
172         // FIXME use dynamic_cast to check type
173         // spice_assert(MAIN_CHANNEL_CLIENT(rcc) != NULL);
174         mcc.reset((MainChannelClient *) rcc);
175     }
176     channels.push_front(red::shared_ptr<RedChannelClient>(rcc));
177     if (during_target_migrate && seamless_migrate) {
178         if (rcc->set_migration_seamless()) {
179             num_migrated_channels++;
180         }
181     }
182 
183 cleanup:
184     pthread_mutex_unlock(&lock);
185     return result;
186 }
187 
get_main()188 MainChannelClient *RedClient::get_main()
189 {
190     return mcc.get();
191 }
192 
semi_seamless_migrate_complete()193 void RedClient::semi_seamless_migrate_complete()
194 {
195     pthread_mutex_lock(&lock);
196     if (!during_target_migrate || seamless_migrate) {
197         spice_error("unexpected");
198         pthread_mutex_unlock(&lock);
199         return;
200     }
201     during_target_migrate = FALSE;
202     FOREACH_CHANNEL_CLIENT(this, rcc) {
203         rcc->semi_seamless_migration_complete();
204     }
205     pthread_mutex_unlock(&lock);
206     reds_on_client_semi_seamless_migrate_complete(reds, this);
207 }
208 
209 /* should be called only from the main thread */
during_migrate_at_target()210 int RedClient::during_migrate_at_target()
211 {
212     int ret;
213     pthread_mutex_lock(&lock);
214     ret = during_target_migrate;
215     pthread_mutex_unlock(&lock);
216     return ret;
217 }
218 
remove_channel(RedChannelClient * rcc)219 void RedClient::remove_channel(RedChannelClient *rcc)
220 {
221     RedClient *client = rcc->get_client();
222     red::shared_ptr<RedChannelClient> holding_rcc(rcc);
223     pthread_mutex_lock(&client->lock);
224     client->channels.remove(holding_rcc);
225     pthread_mutex_unlock(&client->lock);
226 }
227 
228 /* returns TRUE If all channels are finished migrating, FALSE otherwise */
seamless_migration_done_for_channel()229 gboolean RedClient::seamless_migration_done_for_channel()
230 {
231     gboolean ret = FALSE;
232 
233     pthread_mutex_lock(&lock);
234     num_migrated_channels--;
235     /* we assume we always have at least one channel who has migration data transfer,
236      * otherwise, this flag will never be set back to FALSE*/
237     if (!num_migrated_channels) {
238         during_target_migrate = FALSE;
239         seamless_migrate = FALSE;
240         /* migration completion might have been triggered from a different thread
241          * than the main thread */
242         reds_get_main_dispatcher(reds)->seamless_migrate_dst_complete(this);
243         ret = TRUE;
244     }
245     pthread_mutex_unlock(&lock);
246 
247     return ret;
248 }
249 
is_disconnecting()250 gboolean RedClient::is_disconnecting()
251 {
252     gboolean ret;
253     pthread_mutex_lock(&lock);
254     ret =  disconnecting;
255     pthread_mutex_unlock(&lock);
256     return ret;
257 }
258 
set_disconnecting()259 void RedClient::set_disconnecting()
260 {
261     pthread_mutex_lock(&lock);
262     disconnecting = TRUE;
263     pthread_mutex_unlock(&lock);
264 }
265 
get_server()266 RedsState *RedClient::get_server()
267 {
268     return reds;
269 }
270