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
25namespace BPrivate {
26
27
28BPath
29default_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
41BPath
42default_mail_in_directory()
43{
44	BPath path = default_mail_directory();
45	path.Append("in");
46
47	return path;
48}
49
50
51BPath
52default_mail_out_directory()
53{
54	BPath path = default_mail_directory();
55	path.Append("out");
56
57	return path;
58}
59
60
61status_t
62WriteMessageFile(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