f7215ac853
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@9016 a95241bf-73f2-0310-859d-f6bbb57e9c96
184 lines
5.0 KiB
C++
184 lines
5.0 KiB
C++
/* Numail Kit - general header for using the kit
|
|
**
|
|
** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
|
|
*/
|
|
|
|
|
|
#include <Messenger.h>
|
|
#include <String.h>
|
|
#include <Entry.h>
|
|
#include <File.h>
|
|
#include <Path.h>
|
|
#include <Directory.h>
|
|
#include <Locker.h>
|
|
#include <Autolock.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#define timeout 5e5
|
|
|
|
namespace MailInternal {
|
|
|
|
status_t WriteMessageFile(const BMessage& archive, const BPath& path, const char* name);
|
|
|
|
}
|
|
|
|
|
|
status_t MailInternal::WriteMessageFile(const BMessage& archive, const BPath& path, const char* name)
|
|
{
|
|
status_t ret = B_OK;
|
|
BString leaf = name;
|
|
leaf << ".tmp";
|
|
|
|
BEntry settings_entry;
|
|
BFile tmpfile;
|
|
bigtime_t now = system_time();
|
|
|
|
create_directory(path.Path(), 0777);
|
|
{
|
|
BDirectory account_dir(path.Path());
|
|
ret = account_dir.InitCheck();
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't open '%s': %s\n",
|
|
path.Path(), strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
// get an entry for the tempfile
|
|
// Get it here so that failure doesn't create any problems
|
|
ret = settings_entry.SetTo(&account_dir,leaf.String());
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't create an entry for '%s/%s': %s\n",
|
|
path.Path(), leaf.String(), strerror(ret));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Save to a temporary file
|
|
//
|
|
|
|
// Our goal is to write to a tempfile and then use 'rename' to
|
|
// link that file into place once it contains valid contents.
|
|
// Given the filesystem's guarantee of atomic "rename" oper-
|
|
// ations this will guarantee that any non-temp files in the
|
|
// config directory are valid configuration files.
|
|
//
|
|
// Ideally, we would be able to do the following:
|
|
// BFile tmpfile(&account_dir, "tmpfile", B_WRITE_ONLY|B_CREATE_FILE);
|
|
// // ...
|
|
// tmpfile.Relink(&account_dir,"realfile");
|
|
// But this doesn't work because there is no way in the API
|
|
// to link based on file descriptor. (There should be, for
|
|
// exactly this reason, and I see no reason that it can not
|
|
// be added to the API, but as it is not present now so we'll
|
|
// have to deal.) It has to be file-descriptor based because
|
|
// (a) all a BFile knows is its node/FD and (b) the file may
|
|
// be unlinked at any time, at which point renaming the entry
|
|
// to clobber the "realfile" will result in an invalid con-
|
|
// figuration file being created.
|
|
//
|
|
// We can't count on not clobbering the tempfile to gain
|
|
// exclusivity because, if the system crashes between when
|
|
// we create the tempfile an when we rename it, we will have
|
|
// a zombie tempfile that will prevent us from doing any more
|
|
// saves.
|
|
//
|
|
// What we can do is:
|
|
//
|
|
// Create or open the tempfile
|
|
// // At this point no one will *clobber* the file, but
|
|
// // others may open it
|
|
// Lock the tempfile
|
|
// // At this point, no one else may open it and we have
|
|
// // exclusive access to it. Because of the above, we
|
|
// // know that our entry is still valid
|
|
//
|
|
// Truncate the tempfile
|
|
// Write settings
|
|
// Sync
|
|
// Rename to the realfile
|
|
// // this does not affect the lock, but now open-
|
|
// // ing the realfile will fail with B_BUSY
|
|
// Unlock
|
|
//
|
|
// If this code is the only code that changes these files,
|
|
// then we are guaranteed that all realfiles will be valid
|
|
// settings files. I think that's the best we can do unless
|
|
// we get the Relink() api. An implementation of the above
|
|
// follows.
|
|
//
|
|
|
|
// Create or open
|
|
ret = B_TIMED_OUT;
|
|
while (system_time() - now < timeout) //-ATT-no timeout arg. Setting by #define
|
|
{
|
|
ret = tmpfile.SetTo(&settings_entry, B_WRITE_ONLY | B_CREATE_FILE);
|
|
if (ret != B_BUSY) break;
|
|
|
|
// wait 1/100th second
|
|
snooze((bigtime_t)1e4);
|
|
}
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't open '%s/%s' within the timeout period (%fs): %s\n",
|
|
path.Path(), leaf.String(), (float)timeout/1e6, strerror(ret));
|
|
return ret==B_BUSY? B_TIMED_OUT:ret;
|
|
}
|
|
|
|
// lock
|
|
ret = B_TIMED_OUT;
|
|
while (system_time() - now < timeout)
|
|
{
|
|
ret = tmpfile.Lock(); //-ATT-changed account_file to tmpfile. Is that allowed?
|
|
if (ret != B_BUSY) break;
|
|
|
|
// wait 1/100th second
|
|
snooze((bigtime_t)1e4);
|
|
}
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't lock '%s/%s' in within the timeout period (%fs): %s\n",
|
|
path.Path(), leaf.String(), (float)timeout/1e6, strerror(ret));
|
|
// Can't remove it here, since it might be someone else's.
|
|
// Leaving a zombie shouldn't cause any problems tho so
|
|
// that's OK.
|
|
return ret==B_BUSY? B_TIMED_OUT:ret;
|
|
}
|
|
|
|
// truncate
|
|
tmpfile.SetSize(0);
|
|
|
|
// write
|
|
ret = archive.Flatten(&tmpfile);
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't flatten settings to '%s/%s': %s\n",
|
|
path.Path(), leaf.String(), strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
// ensure it's actually writen
|
|
ret = tmpfile.Sync();
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't sync settings to '%s/%s': %s\n",
|
|
path.Path(), leaf.String(), strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
// clobber old settings
|
|
ret = settings_entry.Rename(name,true);
|
|
if (ret != B_OK)
|
|
{
|
|
fprintf(stderr, "Couldn't clobber old settings '%s/%s': %s\n",
|
|
path.Path(), name, strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|