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