import os import stat import pwd import grp import subprocess class ufps: """ Primary class for libufps """ def __init__(self, basedir): """ Constructor method. :param basedir: The input base directory from the user/program. Gets normalized later. :type basedir: str """ # Normalize the directory prior to setting it. self.basedir = self.normalizeInputDir(basedir) # Now we check if the path exists and is a directory. self.checkPathValidity(self.basedir) print ("Init ufps with base dir: " + self.basedir) def normalizeInputDir(self, dir): """ This function normalizes the input directory. In short, it makes sure the input ends with /, as we expect that to be the case in the rest of the code. :param dir: The input directory from the user/program. :type dir: str :return: A string representing a directory that has been normalized as described above. :rtype: str """ if(dir[-1] != '/'): dir=dir+'/' return dir def checkPathValidity(self, dir): """ This function checks the validity of an input path. Path validity means that it (1) exists and (2) is a directory. :raises FileNotFoundError: The path doesn't exist at all. :raises NotADirectoryError: The path is a file. :param dir: Input directory. :type dir: str :return: `True` if path is valid, `False` if not. :rtype: bool """ exist = os.path.exists(dir) isdir = os.path.isdir(dir) # For the purposes of verbosity, we check if a file with the same name # exists by removing the / at the end of the directory. vDir = dir[len(dir)-1] vExist = os.path.exists(vDir) # If vExist is False and exist is False, the user input a nothing burger. # This results in FileNotFoundError being thrown. # If that doesn't occur, and isdir is false, they input a file. # This results in NotADirectoryError being thrown. # If none of the above occurs, the path is valid. if(not exist and not vExist): raise FileNotFoundError("The path " + dir + " does not exist.") if(not isdir): raise NotADirectoryError("The path must be a directory.") return isdir def parsePath(self, path): """ Parses octal permissions stats into a string. Also determines other characteristics of the path: if it's a directory, and if it's a link (and where it goes). Returns this information as well. :param path: Path to file or directory. :type path: str :return pPerms: Path permissions :rtype pPerms: str :return pIsDir: Whether or not the path is a directory :rtype pIsDir: bool :return pIsLink: Whether or not the path is a link :rtype pIsLink: bool :return pLinkTarget: Where the link points to (if applicable) :rtype pLinkTarget: str """ realpath = self.basedir+path # Get stats of the file/directory stats = os.stat(realpath) # Invoke a magic spell # Magic number -3 is used as for this we only need the last 3 digits. # Directory and link detection is handled later. pPerms = oct(stats.st_mode)[-3:] # Get whether or not the path is a dir pIsDir = os.path.isdir(realpath) # Get whether or not the path is a link. pIsLink = os.path.islink(realpath) pLinkTarget = None if pIsLink: pLinkTarget = os.readlink(realpath) return pPerms, pIsDir, pIsLink, pLinkTarget def getFilePermissions(self, path): """ Gets Unix file permissions of a directory or file. :param path: Path to directory or file :type permStr: str :return pPerms: Path permissions :rtype pPerms: str :return pIsDir: Whether or not the path is a directory :rtype pIsDir: bool :return pIsLink: Whether or not the path is a link :rtype pIsLink: bool :return pLinkTarget: Where the link points to (if applicable) :rtype pLinkTarget: str """ # TODO: Consider just moving parsePerms to getFilePermissions pPerms, pIsDir, pIsLink, pLinkTarget = self.parsePath(path) return pPerms, pIsDir, pIsLink, pLinkTarget def getFileOwner(self, path): """ Gets file owner and group of a directory or file. :param path: Path to directory or file :type path: str :return ownername: Owner name :rtype ownername: str :return groupname: Group name :rtype groupname: str """ realpath = self.basedir+path stats = os.stat(realpath) uid = stats.st_uid gid = stats.st_gid ownername = pwd.getpwuid(stats.st_uid).pw_name groupname = grp.getgrgid(stats.st_gid).gr_name return ownername, groupname # def getDirectoryContents(self, path): # realpath = self.basedir + path # contents = os.listdir(path) # return contents # def getInformationR(self, path): # for root, dirs, files in os.walk(path): # for dir in dirs: # # for file in files: def createIndex(self, path): """ Create an idex by delving into subdirectories and recording information. :rtype path: Path to directory or file :type path: str :return outputIndex: Returns file permission, owner, directory, and link information in a string to be written to a file. :rtype outputIndex: str """ outputIndex = "" realpath = self.basedir+path #realpath = path findCommand = "find " + realpath + " -printf '%P\n'" fileList = subprocess.check_output(findCommand, shell=True, text=True) fileList = fileList.split('\n') # Remove tailing newline which causes the root directory to be indexed twice. fileList = fileList[:-1] for p in fileList: pPerms, pIsDir, pIsLink, pLinkTarget = self.getFilePermissions(p) ownername, groupname = self.getFileOwner(p) D_userFriendlyString = pPerms + " | " + ownername + " | " + groupname + " | " + str(pIsDir) + " | " + str(pIsLink) + " | " + p + " | " + str(pLinkTarget) + "\n" outputIndex+=D_userFriendlyString # Remove tailing newline once again outputIndex = outputIndex[:-1] return outputIndex