vdr
1.7.27
|
00001 /* 00002 * videodir.c: Functions to maintain a distributed video directory 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: videodir.c 2.0 2008/02/16 13:00:03 kls Exp $ 00008 */ 00009 00010 #include "videodir.h" 00011 #include <ctype.h> 00012 #include <errno.h> 00013 #include <fcntl.h> 00014 #include <stdio.h> 00015 #include <stdlib.h> 00016 #include <string.h> 00017 #include <sys/stat.h> 00018 #include <unistd.h> 00019 #include "recording.h" 00020 #include "tools.h" 00021 00022 00023 //#define HARDLINK_TEST_ONLY 00024 00025 const char *VideoDirectory = VIDEODIR; 00026 00027 class cVideoDirectory { 00028 private: 00029 char *name, *stored, *adjusted; 00030 int length, number, digits; 00031 public: 00032 cVideoDirectory(void); 00033 ~cVideoDirectory(); 00034 int FreeMB(int *UsedMB = NULL); 00035 const char *Name(void) { return name ? name : VideoDirectory; } 00036 const char *Stored(void) { return stored; } 00037 int Length(void) { return length; } 00038 bool IsDistributed(void) { return name != NULL; } 00039 bool Next(void); 00040 void Store(void); 00041 const char *Adjust(const char *FileName); 00042 }; 00043 00044 cVideoDirectory::cVideoDirectory(void) 00045 { 00046 length = strlen(VideoDirectory); 00047 name = (VideoDirectory[length - 1] == '0') ? strdup(VideoDirectory) : NULL; 00048 stored = adjusted = NULL; 00049 number = -1; 00050 digits = 0; 00051 } 00052 00053 cVideoDirectory::~cVideoDirectory() 00054 { 00055 free(name); 00056 free(stored); 00057 free(adjusted); 00058 } 00059 00060 int cVideoDirectory::FreeMB(int *UsedMB) 00061 { 00062 return FreeDiskSpaceMB(name ? name : VideoDirectory, UsedMB); 00063 } 00064 00065 bool cVideoDirectory::Next(void) 00066 { 00067 if (name) { 00068 if (number < 0) { 00069 int l = length; 00070 while (l-- > 0 && isdigit(name[l])) 00071 ; 00072 l++; 00073 digits = length - l; 00074 int n = atoi(&name[l]); 00075 if (n == 0) 00076 number = n; 00077 else 00078 return false; // base video directory must end with zero 00079 } 00080 if (++number > 0) { 00081 char buf[16]; 00082 if (sprintf(buf, "%0*d", digits, number) == digits) { 00083 strcpy(&name[length - digits], buf); 00084 return DirectoryOk(name); 00085 } 00086 } 00087 } 00088 return false; 00089 } 00090 00091 void cVideoDirectory::Store(void) 00092 { 00093 if (name) { 00094 free(stored); 00095 stored = strdup(name); 00096 } 00097 } 00098 00099 const char *cVideoDirectory::Adjust(const char *FileName) 00100 { 00101 if (stored) { 00102 free(adjusted); 00103 adjusted = strdup(FileName); 00104 return strncpy(adjusted, stored, length); 00105 } 00106 return NULL; 00107 } 00108 00109 cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags) 00110 { 00111 const char *ActualFileName = FileName; 00112 00113 // Incoming name must be in base video directory: 00114 if (strstr(FileName, VideoDirectory) != FileName) { 00115 esyslog("ERROR: %s not in %s", FileName, VideoDirectory); 00116 errno = ENOENT; // must set 'errno' - any ideas for a better value? 00117 return NULL; 00118 } 00119 // Are we going to create a new file? 00120 if ((Flags & O_CREAT) != 0) { 00121 cVideoDirectory Dir; 00122 if (Dir.IsDistributed()) { 00123 // Find the directory with the most free space: 00124 int MaxFree = Dir.FreeMB(); 00125 while (Dir.Next()) { 00126 int Free = FreeDiskSpaceMB(Dir.Name()); 00127 if (Free > MaxFree) { 00128 Dir.Store(); 00129 MaxFree = Free; 00130 } 00131 } 00132 if (Dir.Stored()) { 00133 ActualFileName = Dir.Adjust(FileName); 00134 if (!MakeDirs(ActualFileName, false)) 00135 return NULL; // errno has been set by MakeDirs() 00136 if (symlink(ActualFileName, FileName) < 0) { 00137 LOG_ERROR_STR(FileName); 00138 return NULL; 00139 } 00140 ActualFileName = strdup(ActualFileName); // must survive Dir! 00141 } 00142 } 00143 } 00144 cUnbufferedFile *File = cUnbufferedFile::Create(ActualFileName, Flags, DEFFILEMODE); 00145 if (ActualFileName != FileName) 00146 free((char *)ActualFileName); 00147 return File; 00148 } 00149 00150 int CloseVideoFile(cUnbufferedFile *File) 00151 { 00152 int Result = File->Close(); 00153 delete File; 00154 return Result; 00155 } 00156 00157 bool RenameVideoFile(const char *OldName, const char *NewName) 00158 { 00159 // Only the base video directory entry will be renamed, leaving the 00160 // possible symlinks untouched. Going through all the symlinks and disks 00161 // would be unnecessary work - maybe later... 00162 if (rename(OldName, NewName) == -1) { 00163 LOG_ERROR_STR(OldName); 00164 return false; 00165 } 00166 return true; 00167 } 00168 00169 bool RemoveVideoFile(const char *FileName) 00170 { 00171 return RemoveFileOrDir(FileName, true); 00172 } 00173 00174 static bool StatNearestDir(const char *FileName, struct stat *Stat) 00175 { 00176 cString Name(FileName); 00177 char *p; 00178 while ((p = strrchr((char*)(const char*)Name + 1, '/')) != NULL) { 00179 *p = 0; // truncate at last '/' 00180 if (stat(Name, Stat) == 0) { 00181 isyslog("StatNearestDir: Stating %s", (const char*)Name); 00182 return true; 00183 } 00184 } 00185 return false; 00186 } 00187 00188 bool HardLinkVideoFile(const char *OldName, const char *NewName) 00189 { 00190 // Incoming name must be in base video directory: 00191 if (strstr(OldName, VideoDirectory) != OldName) { 00192 esyslog("ERROR: %s not in %s", OldName, VideoDirectory); 00193 return false; 00194 } 00195 if (strstr(NewName, VideoDirectory) != NewName) { 00196 esyslog("ERROR: %s not in %s", NewName, VideoDirectory); 00197 return false; 00198 } 00199 00200 const char *ActualNewName = NewName; 00201 cString ActualOldName(ReadLink(OldName), true); 00202 00203 // Some safety checks: 00204 struct stat StatOldName; 00205 if (lstat(ActualOldName, &StatOldName) == 0) { 00206 if (S_ISLNK(StatOldName.st_mode)) { 00207 esyslog("HardLinkVideoFile: Failed to resolve symbolic link %s", (const char*)ActualOldName); 00208 return false; 00209 } 00210 } 00211 else { 00212 esyslog("HardLinkVideoFile: lstat failed on %s", (const char*)ActualOldName); 00213 return false; 00214 } 00215 isyslog("HardLinkVideoFile: %s is on %i", (const char*)ActualOldName, (int)StatOldName.st_dev); 00216 00217 // Find the video directory where ActualOldName is located 00218 00219 cVideoDirectory Dir; 00220 struct stat StatDir; 00221 if (!StatNearestDir(NewName, &StatDir)) { 00222 esyslog("HardLinkVideoFile: stat failed on %s", NewName); 00223 return false; 00224 } 00225 00226 isyslog("HardLinkVideoFile: %s is on %i", NewName, (int)StatDir.st_dev); 00227 if (StatDir.st_dev != StatOldName.st_dev) { 00228 // Not yet found. 00229 00230 if (!Dir.IsDistributed()) { 00231 esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName); 00232 return false; 00233 } 00234 00235 // Search in video01 and upwards 00236 bool found = false; 00237 while (Dir.Next()) { 00238 Dir.Store(); 00239 const char *TmpNewName = Dir.Adjust(NewName); 00240 if (StatNearestDir(TmpNewName, &StatDir) && StatDir.st_dev == StatOldName.st_dev) { 00241 isyslog("HardLinkVideoFile: %s is on %i (match)", TmpNewName, (int)StatDir.st_dev); 00242 ActualNewName = TmpNewName; 00243 found = true; 00244 break; 00245 } 00246 isyslog("HardLinkVideoFile: %s is on %i", TmpNewName, (int)StatDir.st_dev); 00247 } 00248 if (ActualNewName == NewName) { 00249 esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName); 00250 return false; 00251 } 00252 00253 // Looking good, we have a match. Create necessary folders. 00254 if (!MakeDirs(ActualNewName, false)) 00255 return false; 00256 // There's no guarantee that the directory of ActualNewName 00257 // is on the same device as the dir that StatNearestDir found. 00258 // But worst case is that the link fails. 00259 } 00260 00261 #ifdef HARDLINK_TEST_ONLY 00262 // Do the hard link to *.vdr_ for testing only 00263 char *name = NULL; 00264 asprintf(&name, "%s_",ActualNewName); 00265 link(ActualOldName, name); 00266 free(name); 00267 return false; 00268 #endif // HARDLINK_TEST_ONLY 00269 00270 // Try creating the hard link 00271 if (link(ActualOldName, ActualNewName) != 0) { 00272 // Failed to hard link. Maybe not allowed on file system. 00273 LOG_ERROR_STR(ActualNewName); 00274 isyslog("HardLinkVideoFile: failed to hard link from %s to %s", (const char*)ActualOldName, ActualNewName); 00275 return false; 00276 } 00277 00278 if (ActualNewName != NewName) { 00279 // video01 and up. Do the remaining symlink 00280 if (symlink(ActualNewName, NewName) < 0) { 00281 LOG_ERROR_STR(NewName); 00282 return false; 00283 } 00284 } 00285 return true; 00286 } 00287 00288 bool VideoFileSpaceAvailable(int SizeMB) 00289 { 00290 cVideoDirectory Dir; 00291 if (Dir.IsDistributed()) { 00292 if (Dir.FreeMB() >= SizeMB * 2) // base directory needs additional space 00293 return true; 00294 while (Dir.Next()) { 00295 if (Dir.FreeMB() >= SizeMB) 00296 return true; 00297 } 00298 return false; 00299 } 00300 return Dir.FreeMB() >= SizeMB; 00301 } 00302 00303 int VideoDiskSpace(int *FreeMB, int *UsedMB) 00304 { 00305 int free = 0, used = 0; 00306 int deleted = DeletedRecordings.TotalFileSizeMB(); 00307 cVideoDirectory Dir; 00308 do { 00309 int u; 00310 free += Dir.FreeMB(&u); 00311 used += u; 00312 } while (Dir.Next()); 00313 if (deleted > used) 00314 deleted = used; // let's not get beyond 100% 00315 free += deleted; 00316 used -= deleted; 00317 if (FreeMB) 00318 *FreeMB = free; 00319 if (UsedMB) 00320 *UsedMB = used; 00321 return (free + used) ? used * 100 / (free + used) : 0; 00322 } 00323 00324 cString PrefixVideoFileName(const char *FileName, char Prefix) 00325 { 00326 char PrefixedName[strlen(FileName) + 2]; 00327 00328 const char *p = FileName + strlen(FileName); // p points at the terminating 0 00329 int n = 2; 00330 while (p-- > FileName && n > 0) { 00331 if (*p == '/') { 00332 if (--n == 0) { 00333 int l = p - FileName + 1; 00334 strncpy(PrefixedName, FileName, l); 00335 PrefixedName[l] = Prefix; 00336 strcpy(PrefixedName + l + 1, p + 1); 00337 return PrefixedName; 00338 } 00339 } 00340 } 00341 return NULL; 00342 } 00343 00344 cString NewVideoFileName(const char *FileName, const char *NewDirName) 00345 { 00346 char *NewDir = ExchangeChars(strdup(NewDirName), true); 00347 if (NewDir) { 00348 const char *p = FileName + strlen(FileName); // p points at the terminating 0 00349 while (p-- > FileName) { 00350 if (*p == '/') 00351 break; 00352 } 00353 cString NewName = cString::sprintf("%s/%s%s", VideoDirectory, NewDir, p); 00354 free(NewDir); 00355 return NewName; 00356 } 00357 return NULL; 00358 } 00359 00360 void RemoveEmptyVideoDirectories(void) 00361 { 00362 cVideoDirectory Dir; 00363 do { 00364 RemoveEmptyDirectories(Dir.Name()); 00365 } while (Dir.Next()); 00366 } 00367 00368 bool IsOnVideoDirectoryFileSystem(const char *FileName) 00369 { 00370 cVideoDirectory Dir; 00371 do { 00372 if (EntriesOnSameFileSystem(Dir.Name(), FileName)) 00373 return true; 00374 } while (Dir.Next()); 00375 return false; 00376 }