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