xref: /haiku/src/kits/mail/numailkit.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
1 /* Numail Kit - general header for using the kit
2 **
3 ** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
4 */
5 
6 
7 #include <Messenger.h>
8 #include <String.h>
9 #include <Entry.h>
10 #include <File.h>
11 #include <Path.h>
12 #include <Directory.h>
13 #include <Locker.h>
14 #include <Autolock.h>
15 
16 #include <stdio.h>
17 
18 #define timeout 5e5
19 
20 namespace MailInternal {
21 
22 status_t WriteMessageFile(const BMessage& archive, const BPath& path, const char* name);
23 
24 }
25 
26 
27 status_t MailInternal::WriteMessageFile(const BMessage& archive, const BPath& path, const char* name)
28 {
29 	status_t ret = B_OK;
30 	BString leaf = name;
31 	leaf << ".tmp";
32 
33 	BEntry settings_entry;
34 	BFile tmpfile;
35 	bigtime_t now = system_time();
36 
37 	create_directory(path.Path(), 0777);
38 	{
39 		BDirectory account_dir(path.Path());
40 		ret = account_dir.InitCheck();
41 		if (ret != B_OK)
42 		{
43 			fprintf(stderr, "Couldn't open '%s': %s\n",
44 				path.Path(), strerror(ret));
45 			return ret;
46 		}
47 
48 		// get an entry for the tempfile
49 		// Get it here so that failure doesn't create any problems
50 		ret = settings_entry.SetTo(&account_dir,leaf.String());
51 		if (ret != B_OK)
52 		{
53 			fprintf(stderr, "Couldn't create an entry for '%s/%s': %s\n",
54 				path.Path(), leaf.String(), strerror(ret));
55 			return ret;
56 		}
57 	}
58 
59 	//
60 	// Save to a temporary file
61 	//
62 
63 	// Our goal is to write to a tempfile and then use 'rename' to
64 	// link that file into place once it contains valid contents.
65 	// Given the filesystem's guarantee of atomic "rename" oper-
66 	// ations this will guarantee that any non-temp files in the
67 	// config directory are valid configuration files.
68 	//
69 	// Ideally, we would be able to do the following:
70 	//   BFile tmpfile(&account_dir, "tmpfile", B_WRITE_ONLY|B_CREATE_FILE);
71 	//   // ...
72 	//   tmpfile.Relink(&account_dir,"realfile");
73 	// But this doesn't work because there is no way in the API
74 	// to link based on file descriptor.  (There should be, for
75 	// exactly this reason, and I see no reason that it can not
76 	// be added to the API, but as it is not present now so we'll
77 	// have to deal.)  It has to be file-descriptor based because
78 	// (a) all a BFile knows is its node/FD and (b) the file may
79 	// be unlinked at any time, at which point renaming the entry
80 	// to clobber the "realfile" will result in an invalid con-
81 	// figuration file being created.
82 	//
83 	// We can't count on not clobbering the tempfile to gain
84 	// exclusivity because, if the system crashes between when
85 	// we create the tempfile an when we rename it, we will have
86 	// a zombie tempfile that will prevent us from doing any more
87 	// saves.
88 	//
89 	// What we can do is:
90 	//
91 	//    Create or open the tempfile
92 	//    // At this point no one will *clobber* the file, but
93 	//    // others may open it
94 	//    Lock the tempfile
95 	//    // At this point, no one else may open it and we have
96 	//    // exclusive access to it.  Because of the above, we
97 	//    // know that our entry is still valid
98 	//
99 	//    Truncate the tempfile
100 	//    Write settings
101 	//    Sync
102 	//    Rename to the realfile
103 	//    // this does not affect the lock, but now open-
104 	//    // ing the realfile will fail with B_BUSY
105 	//    Unlock
106 	//
107 	// If this code is the only code that changes these files,
108 	// then we are guaranteed that all realfiles will be valid
109 	// settings files.  I think that's the best we can do unless
110 	// we get the Relink() api.  An implementation of the above
111 	// follows.
112 	//
113 
114 	// Create or open
115 	ret = B_TIMED_OUT;
116 	while (system_time() - now < timeout) //-ATT-no timeout arg. Setting by #define
117 	{
118 		ret = tmpfile.SetTo(&settings_entry, B_WRITE_ONLY | B_CREATE_FILE);
119 		if (ret != B_BUSY) break;
120 
121 		// wait 1/100th second
122 		snooze((bigtime_t)1e4);
123 	}
124 	if (ret != B_OK)
125 	{
126 		fprintf(stderr, "Couldn't open '%s/%s' within the timeout period (%fs): %s\n",
127 			path.Path(), leaf.String(), (float)timeout/1e6, strerror(ret));
128 		return ret==B_BUSY? B_TIMED_OUT:ret;
129 	}
130 
131 	// lock
132 	ret = B_TIMED_OUT;
133 	while (system_time() - now < timeout)
134 	{
135 		ret = tmpfile.Lock(); //-ATT-changed account_file to tmpfile. Is that allowed?
136 		if (ret != B_BUSY) break;
137 
138 		// wait 1/100th second
139 		snooze((bigtime_t)1e4);
140 	}
141 	if (ret != B_OK)
142 	{
143 		fprintf(stderr, "Couldn't lock '%s/%s' in within the timeout period (%fs): %s\n",
144 			path.Path(), leaf.String(), (float)timeout/1e6, strerror(ret));
145 		// Can't remove it here, since it might be someone else's.
146 		// Leaving a zombie shouldn't cause any problems tho so
147 		// that's OK.
148 		return ret==B_BUSY? B_TIMED_OUT:ret;
149 	}
150 
151 	// truncate
152 	tmpfile.SetSize(0);
153 
154 	// write
155 	ret = archive.Flatten(&tmpfile);
156 	if (ret != B_OK)
157 	{
158 		fprintf(stderr, "Couldn't flatten settings to '%s/%s': %s\n",
159 			path.Path(), leaf.String(), strerror(ret));
160 		return ret;
161 	}
162 
163 	// ensure it's actually writen
164 	ret = tmpfile.Sync();
165 	if (ret != B_OK)
166 	{
167 		fprintf(stderr, "Couldn't sync settings to '%s/%s': %s\n",
168 			path.Path(), leaf.String(), strerror(ret));
169 		return ret;
170 	}
171 
172 	// clobber old settings
173 	ret = settings_entry.Rename(name,true);
174 	if (ret != B_OK)
175 	{
176 		fprintf(stderr, "Couldn't clobber old settings '%s/%s': %s\n",
177 			path.Path(), name, strerror(ret));
178 		return ret;
179 	}
180 
181 	return B_OK;
182 }
183 
184