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