Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / programs / dtmail / libDtMail / Common / FileShare.C
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /*
24  *+SNOTICE
25  *
26  *
27  *      $TOG: FileShare.C /main/6 1999/03/26 16:52:00 mgreess $
28  *
29  *      RESTRICTED CONFIDENTIAL INFORMATION:
30  *      
31  *      The information in this document is subject to special
32  *      restrictions in a confidential disclosure agreement bertween
33  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
34  *      document outside HP, IBM, Sun, USL, SCO, or Univel wihtout
35  *      Sun's specific written approval.  This documment and all copies
36  *      and derivative works thereof must be returned or destroyed at
37  *      Sun's request.
38  *
39  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
40  *
41  *+ENOTICE
42  */
43
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <fcntl.h>
49
50 #include <DtMail/FileShare.hh>
51 #include <DtMail/DtMailXtProc.h>
52
53 XtAppContext    DtMailDamageContext = NULL;
54
55 static const int FileShareTimeout = 900000;
56 static int tlock_flag = 1;
57
58
59 static Tt_message msg_create(char *op, char *file, Tt_class tt_class, Tt_message_callback f)
60 {
61   Tt_message msg = tt_message_create();
62   
63   // Create the tooltalk message
64   if (tt_ptr_error(msg) != TT_OK) {
65     return ((Tt_message) NULL);
66   }
67   
68   /* Set the message class type */
69   if (tt_message_class_set (msg, tt_class) != TT_OK) {
70     tt_message_destroy(msg);
71     return ((Tt_message) NULL);
72   }
73   
74   /* Set the message address */
75   if (tt_message_address_set (msg, TT_PROCEDURE) != TT_OK) {
76     tt_message_destroy(msg);
77     return ((Tt_message) NULL);
78   }
79   
80   /* Set the disposition of the message */
81   if (tt_message_disposition_set (msg, TT_DISCARD) != TT_OK) {
82     tt_message_destroy(msg);
83     return ((Tt_message) NULL);
84   }
85   
86   /* Set the message operation. */
87   if (tt_message_op_set (msg, op) != TT_OK) {
88     tt_message_destroy(msg);
89     return ((Tt_message) NULL);
90   }
91   
92   /* Set the message scope */
93   if (tt_message_scope_set (msg, TT_FILE) != TT_OK) {
94     tt_message_destroy(msg);
95     return ((Tt_message) NULL);
96   }
97   
98   if (tt_message_file_set (msg, file) != TT_OK) {  
99     tt_message_destroy(msg);
100     return ((Tt_message) NULL);
101   }
102
103   if (tt_message_arg_add(msg, TT_IN, "DtMail", "lock") != TT_OK) {
104     tt_message_destroy(msg);
105     return ((Tt_message) NULL);
106   }
107
108   if (f) {
109     if (tt_message_callback_add(msg, f) != TT_OK) {
110       tt_message_destroy(msg);
111       return ((Tt_message) NULL);
112     }
113   }
114
115   return (msg);
116 }
117
118 Tt_callback_action
119 FileShare::mt_lock_cb(Tt_message m, Tt_pattern p)
120 {
121   Tt_state state = tt_message_state(m);
122   char *op;
123   char *flag = NULL;
124
125   op = tt_message_op(m);
126   if (!op) {
127     return TT_CALLBACK_CONTINUE;
128   }
129
130   flag = tt_message_arg_val(m, 0);
131
132   if (!strcmp(op, "tlock")) {
133     // handle tlock request
134     if (state == TT_HANDLED) {
135       tlock_flag = -1;
136       tt_message_destroy(m);
137     } else if (state == TT_FAILED) {
138       tlock_flag = 0;
139       tt_message_destroy(m);
140     } else if (state == TT_SENT) {
141       if (tt_ptr_error(flag) == TT_OK) {
142         // this message is from another dtmail, ignore it
143         return TT_CALLBACK_CONTINUE;
144       }
145
146       tt_message_reply(m);
147     }
148   } else if (!strcmp(op, "rulock")) {
149     // handle rulock notice
150     if (state == TT_SENT) {
151       DtMailBoolean answer = DTM_FALSE;         // default is to not give up the lock
152       FileShare *f = (FileShare *)tt_pattern_user(p, 1);
153       if (f->_cb_func)
154       {
155         char *msg =
156           DtMailEnv::getMessageText(
157                 FileShareMsgSet, 5,
158                 "Another user would like your lock.");
159         answer = f->_cb_func(DTMC_UNLOCK, f->_path, msg, f->_cb_data);
160       }
161       tt_message_destroy(m);
162     }
163   }
164
165   tt_free(op);
166   return TT_CALLBACK_PROCESSED;
167 }
168
169 FileShare::FileShare(DtMailEnv & error,
170                      DtMail::Session * session,
171                      const char * path,
172                      DtMailCallback cb,
173                      void * clientData)
174 {
175   DtMail::MailRc *mailrc = session->mailRc(error);
176
177     _key = session->newObjectKey();
178     _session = session;
179     _path = strdup(path);
180     _cb_func = cb;
181     _cb_data = clientData;
182
183     // For now, assume we can't write to the file.
184     //
185     _have_write_access = DTM_FALSE;
186     _other_modified = DTM_TRUE;
187     _mt_pattern = NULL;
188     error.clear();
189
190     // Register the file pattern.
191     //
192     _tt_handle = new TTHandle;
193     _tt_handle->session = _session;
194     _tt_handle->key = _key;
195     _tt_handle->self = this;
196
197     _file_pats = ttdt_file_join(_path, TT_FILE, 0, fileCB, _tt_handle);
198     if (tt_pointer_error(_file_pats) != TT_OK) {
199         error.setError(DTME_TTFailure);
200         _file_pats = NULL;
201         return;
202     }
203
204   // isModified(error);
205 }
206
207 FileShare::~FileShare(void)
208 {
209
210     if (_have_write_access == DTM_TRUE && _file_pats) {
211         _pending = PENDING_DESTROY;
212         
213         // ttdt_file_event(NULL, TTDT_SAVED, _file_pats, 1);
214
215         _session->removeObjectKey(_key);
216     }
217
218     if (NULL != _tt_handle)
219       delete _tt_handle;
220
221     if (_file_pats) {
222       ttdt_file_quit(_file_pats, 1);
223     }
224
225     if (_mt_pattern) {
226       tt_pattern_destroy(_mt_pattern);
227       _mt_pattern = NULL;
228     }
229
230     if (_path) {
231         free(_path);
232     }
233
234     _have_write_access = DTM_FALSE;
235     _file_pats = NULL;
236 }
237
238 DtMailBoolean
239 FileShare::isModified(DtMailEnv & error)
240 {
241     error.clear();
242
243     if (!_path) {
244         return(DTM_FALSE);
245     }
246
247     DtMailBoolean       answer = DTM_FALSE;
248
249     Tt_message mt_msg;
250
251     mt_msg = msg_create("tlock", _path, TT_REQUEST, mt_lock_cb);
252     if (mt_msg == NULL) {
253       error.setError(DTME_TTFailure);
254       return DTM_TRUE;
255     }
256
257     if (tt_message_send(mt_msg) != TT_OK) {
258       error.setError(DTME_TTFailure);
259       return DTM_TRUE;
260     }
261
262     tttk_block_while((XtAppContext)0, &tlock_flag, FileShareTimeout);
263
264     // mt_lock_cb sets tlock_flag to -1 if mbox is locked
265     if (tlock_flag == -1) {
266       tlock_flag = 1;   // reset the tlock_flag
267       _other_modified = DTM_TRUE;
268       _mt_lock = DTM_TRUE;
269
270       return DTM_TRUE;
271     } else {
272       // else tlock_flag == 0, means no lock on this mbox
273       // or tlock_flag == 1, means time out
274       tlock_flag = 1;   // reset the tlock_flag
275       _mt_lock = DTM_FALSE;
276
277       // now let's try the dtmail protocol
278       if (ttdt_Get_Modified(NULL, _path, TT_FILE, NULL, FileShareTimeout)) {
279         answer = DTM_TRUE;
280         _other_modified = DTM_TRUE;
281       } else {
282         answer = DTM_FALSE;
283         _other_modified = DTM_FALSE;
284       }
285
286       return(answer);
287     }
288 }
289
290 void
291 FileShare::lockFile(DtMailEnv & error)
292 {
293     int always;
294
295     error.clear();
296
297     // If we have the access, then we locked it before. Simply return.
298     //
299     if (_have_write_access == DTM_TRUE) {
300         return;
301     }
302
303     // First step in locking is determining if anyone else has the lock.
304     // If they do, then we need to ask them to save their changes and
305     // exit.
306     //
307     if (isModified(error) == DTM_TRUE) {
308       DtMailBoolean take_lock = DTM_FALSE;      // default is to not request access
309
310       // calls syncViewAndStoreCallback which then calls syncViewAndStore
311       if (_cb_func)
312       {
313         char *msg =
314           DtMailEnv::getMessageText(
315                 FileShareMsgSet, 6,
316                 "Another session has this mailbox locked. Request access?");
317         take_lock = _cb_func(DTMC_QUERYLOCK, _path, msg, _cb_data);
318       }
319
320       if (take_lock == DTM_FALSE) {
321         error.setError(DTME_OtherOwnsWrite);
322         return;
323       }
324
325       // isModified sets _mt_lock to DTM_TRUE is the mailbox is locked
326       // by mailtool
327       if (_mt_lock == DTM_TRUE) {
328         // mailtool style locking
329         Tt_message mt_msg;
330
331         mt_msg = msg_create("rulock", _path, TT_NOTICE, NULL);
332         if (mt_msg == NULL) {
333           error.setError(DTME_TTFailure);
334           return;
335         }
336
337         if (tt_message_send(mt_msg) != TT_OK) {
338           error.setError(DTME_TTFailure);
339           return;
340         }
341         tt_message_destroy(mt_msg);
342       } else {
343         // ttdt style locking
344         ttdt_Save(NULL, _path, TT_FILE, DtMailDamageContext, FileShareTimeout);
345       }
346
347       // Give the other mailer FileShareTimeout seconds to give up the lock
348       time_t t_start;
349
350       time(&t_start);
351       while (1) {
352         sleep(5);
353         if (isModified(error) == DTM_FALSE) {
354           break;
355         } else {
356           if (time((time_t)NULL) - t_start > FileShareTimeout) {
357             // time out!
358             error.setError(DTME_OtherOwnsWrite);
359             return;
360           }
361         }
362       }
363     }
364
365     // Set this so we don't call our client during this handshake.
366     //
367     _pending = PENDING_LOCK;
368     _outstanding = DTM_TRUE;
369
370     // Now we are ready to lock the mailbox
371
372     // register this pattern so we can handle messages from mailtool
373     _mt_pattern = tt_pattern_create();
374     tt_pattern_category_set(_mt_pattern, TT_HANDLE);
375     tt_pattern_scope_add(_mt_pattern, TT_FILE);
376     tt_pattern_file_add(_mt_pattern, _path);
377     tt_pattern_op_add(_mt_pattern, "tlock");
378     tt_pattern_op_add(_mt_pattern, "rulock");
379     tt_pattern_callback_add(_mt_pattern, mt_lock_cb);
380     tt_pattern_user_set(_mt_pattern, 1, (void *)this);
381
382     if (tt_pattern_register(_mt_pattern) != TT_OK) {
383       error.setError(DTME_TTFailure);
384       return;
385     }
386
387     // Send the message saying we want to be the owner.
388     ttdt_file_event(NULL, TTDT_MODIFIED, _file_pats, 1);
389
390     // We need to process any messages that have arrived. We will get our own
391     // modified message, which is not terribly interesting. What is interesting
392     // is a modified message from someone else. That means that we have a race
393     // condition where two processes both asked if the file was being modified,
394     // and it wasn't. Then both said they were the owner, which is obviously
395     // wrong so we need to blow both off and make them try again. Hopefully
396     // there is enough randomness in our clients that the race condition will
397     // clear itself up and we won't get here very often.
398     //
399     always = 1;
400     while(_outstanding == DTM_TRUE) {
401       tttk_block_while((XtAppContext)0, &always, 0);
402     }
403
404     if (_other_modified == DTM_TRUE) {
405       // Well, we have a race. Fail this lock as will the other process,
406       // we hope.
407       error.setError(DTME_OtherOwnsWrite);
408       return;
409     }
410
411     // Okay, we now have the lock.
412     _have_write_access = DTM_TRUE;
413 }
414
415 DtMailBoolean
416 FileShare::readOnly(DtMailEnv & error)
417 {
418   DtMailBoolean answer = DTM_TRUE;      // default is to accept read-only access
419
420   if (_cb_func)
421   {
422     char *msg =
423       DtMailEnv::getMessageText(
424                 FileShareMsgSet, 7,
425                 "Unable to obtain lock, open this mailbox as read only?");
426     answer = _cb_func(DTMC_READONLY, _path, msg, _cb_data);
427   }
428
429   if (answer)
430     error.clear();
431
432   return(answer);
433 }
434
435 DtMailBoolean
436 FileShare::readWriteOverride(DtMailEnv & error)
437 {
438   DtMailBoolean answer = DTM_FALSE;     // default is to open for read-only access
439
440   if (_cb_func)
441   {
442     char *msg =
443       DtMailEnv::getMessageText(
444                 FileShareMsgSet, 8,
445                 "Unable to obtain lock because system not responding, open this mailbox as read only, read write, or cancel?");
446     answer = _cb_func(DTMC_READWRITEOVERRIDE, _path, msg, _cb_data);
447   }
448
449   if (answer == ((DtMailBoolean)((DTM_FALSE+DTM_TRUE)*2))) {
450     error.setError(DTME_UserInterrupted);
451     answer = DTM_FALSE;
452   }
453   else {
454     error.clear();
455   }
456
457   return(answer);
458 }
459
460 #ifdef DEAD_WOOD
461 DtMailBoolean
462 FileShare::locked(void)
463 {
464     return(_have_write_access);
465 }
466 #endif /* DEAD_WOOD */
467
468 Tt_message 
469 FileShare::fileCB(Tt_message msg,
470                   Tttk_op op,
471                   char * path,
472                   void *clientData,
473                   int,
474                   int same_proc)
475 {
476     TTHandle    *tt_handle = (TTHandle *)clientData;
477     DtMailBoolean answer;
478
479     if (tt_handle->session->validObjectKey(tt_handle->key) == DTM_FALSE) {
480         // This object has been destroyed. We got here most likely because
481         // ToolTalk is responding to one of our clean up messages. In any
482         // case, fail the message and return.
483         //
484         tttk_message_fail(msg, TT_DESKTOP_ECANCELED, "Object destroyed", 1);
485         return(0);
486     }
487
488     FileShare * self = tt_handle->self;
489
490     switch(op) {
491       case TTDT_MODIFIED:
492         if (self->_outstanding == DTM_FALSE && !same_proc) {
493           if (self->_cb_func)
494           {
495             char *msg =
496               DtMailEnv::getMessageText(
497                 FileShareMsgSet, 9,
498                 "Another user has taken your lock.");
499             self->_cb_func(DTMC_LOSTLOCK, path, msg, self->_cb_data);
500           }
501           self->_other_modified = DTM_TRUE;
502           self->_have_write_access = DTM_FALSE;
503           break;
504         }
505         
506         if (self->_outstanding == DTM_TRUE && self->_pending == PENDING_LOCK) {
507             // This could be one of 2 conditions. If the message is
508             // from us, then we have the lock, and we are done.
509             // If not, then someone else is asking for the lock. We
510             // reflect this by giving them the lock.
511             //
512             if (same_proc) {
513                 self->_other_modified = DTM_FALSE;
514                 self->_have_write_access = DTM_TRUE;
515                 self->_outstanding = DTM_FALSE;
516             }
517             else {
518                 self->_other_modified = DTM_TRUE;
519                 self->_have_write_access = DTM_FALSE;
520                 // We haven't seen our own request yet. Leave outstanding
521                 // so we can process it before leaving.
522             }
523         }
524         break;
525
526       case TTDT_GET_MODIFIED:
527         tt_message_arg_ival_set(msg, 1, 1);
528         tt_message_reply(msg);
529         break;
530
531       case TTDT_SAVED:
532       case TTDT_REVERTED:
533         // The other process has saved their changes (or tossed them).
534         // At this point we should be able to start modifying the file.
535         //
536         self->_other_modified = DTM_FALSE;
537         if (self->_outstanding == DTM_TRUE && self->_pending == PENDING_SAVE) {
538             self->_outstanding = DTM_FALSE;
539         }
540         break;
541
542       case TTDT_REVERT:
543       case TTDT_SAVE:
544         // Someone is asking us to save our changes and close the file.
545         //
546         answer = DTM_FALSE;     // default is to not give up the lock
547
548         if (self->_cb_func)
549         {
550           char *msg =
551               DtMailEnv::getMessageText(
552                 FileShareMsgSet, 5,
553                 "Another user would like your lock.");
554           answer = self->_cb_func(DTMC_UNLOCK, path, msg, self->_cb_data);
555         }
556
557         if (answer == DTM_TRUE) {
558           tt_message_reply(msg);
559         } else {
560           tttk_message_fail(msg, TT_DESKTOP_EACCES, 0, 0);
561         }
562         break;
563
564       default:
565         // Other messages, we simply smile and say thank you.:-)
566         //
567         tt_message_reply(msg);
568         break;
569
570     }
571
572     tt_message_destroy(msg);
573     return(0);
574 }
575