vdr  1.7.27
videodir.c
Go to the documentation of this file.
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 }