1## @file
2# Install distribution package.
3#
4# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
5#
6# SPDX-License-Identifier: BSD-2-Clause-Patent
7#
8
9'''
10RmPkg
11'''
12
13##
14# Import Modules
15#
16import os.path
17from stat import S_IWUSR
18from traceback import format_exc
19from platform import python_version
20from hashlib import md5
21from sys import stdin
22from sys import platform
23
24from Core.DependencyRules import DependencyRules
25from Library import GlobalData
26from Logger import StringTable as ST
27import Logger.Log as Logger
28from Logger.ToolError import OPTION_MISSING
29from Logger.ToolError import UNKNOWN_ERROR
30from Logger.ToolError import ABORT_ERROR
31from Logger.ToolError import CODE_ERROR
32from Logger.ToolError import FatalError
33
34
35## CheckDpDepex
36#
37# Check if the Depex is satisfied
38# @param Dep: Dep
39# @param Guid: Guid of Dp
40# @param Version: Version of Dp
41# @param WorkspaceDir: Workspace Dir
42#
43def CheckDpDepex(Dep, Guid, Version, WorkspaceDir):
44    (Removable, DependModuleList) = Dep.CheckDpDepexForRemove(Guid, Version)
45    if not Removable:
46        Logger.Info(ST.MSG_CONFIRM_REMOVE)
47        Logger.Info(ST.MSG_USER_DELETE_OP)
48        Input = stdin.readline()
49        Input = Input.replace('\r', '').replace('\n', '')
50        if Input.upper() != 'Y':
51            Logger.Error("RmPkg", UNKNOWN_ERROR, ST.ERR_USER_INTERRUPT)
52            return 1
53        else:
54            #
55            # report list of modules that are not valid due to force
56            # remove,
57            # also generate a log file for reference
58            #
59            Logger.Info(ST.MSG_INVALID_MODULE_INTRODUCED)
60            LogFilePath = os.path.normpath(os.path.join(WorkspaceDir, GlobalData.gINVALID_MODULE_FILE))
61            Logger.Info(ST.MSG_CHECK_LOG_FILE % LogFilePath)
62            try:
63                LogFile = open(LogFilePath, 'w')
64                try:
65                    for ModulePath in DependModuleList:
66                        LogFile.write("%s\n"%ModulePath)
67                        Logger.Info(ModulePath)
68                except IOError:
69                    Logger.Warn("\nRmPkg", ST.ERR_FILE_WRITE_FAILURE,
70                                File=LogFilePath)
71            except IOError:
72                Logger.Warn("\nRmPkg", ST.ERR_FILE_OPEN_FAILURE,
73                            File=LogFilePath)
74            finally:
75                LogFile.close()
76
77## Remove Path
78#
79# removing readonly file on windows will get "Access is denied"
80# error, so before removing, change the mode to be writeable
81#
82# @param Path: The Path to be removed
83#
84def RemovePath(Path):
85    Logger.Info(ST.MSG_REMOVE_FILE % Path)
86    if not os.access(Path, os.W_OK):
87        os.chmod(Path, S_IWUSR)
88    os.remove(Path)
89    try:
90        os.removedirs(os.path.split(Path)[0])
91    except OSError:
92        pass
93## GetCurrentFileList
94#
95# @param DataBase: DataBase of UPT
96# @param Guid: Guid of Dp
97# @param Version: Version of Dp
98# @param WorkspaceDir: Workspace Dir
99#
100def GetCurrentFileList(DataBase, Guid, Version, WorkspaceDir):
101    NewFileList = []
102    for Dir in  DataBase.GetDpInstallDirList(Guid, Version):
103        RootDir = os.path.normpath(os.path.join(WorkspaceDir, Dir))
104        for Root, Dirs, Files in os.walk(RootDir):
105            Logger.Debug(0, Dirs)
106            for File in Files:
107                FilePath = os.path.join(Root, File)
108                if FilePath not in NewFileList:
109                    NewFileList.append(FilePath)
110    return NewFileList
111
112
113## Tool entrance method
114#
115# This method mainly dispatch specific methods per the command line options.
116# If no error found, return zero value so the caller of this tool can know
117# if it's executed successfully or not.
118#
119# @param  Options: command option
120#
121def Main(Options = None):
122
123    try:
124        DataBase = GlobalData.gDB
125        if not Options.DistributionFile:
126            Logger.Error("RmPkg",
127                         OPTION_MISSING,
128                         ExtraData=ST.ERR_SPECIFY_PACKAGE)
129        WorkspaceDir = GlobalData.gWORKSPACE
130        #
131        # Prepare check dependency
132        #
133        Dep = DependencyRules(DataBase)
134
135        #
136        # Get the Dp information
137        #
138        StoredDistFile, Guid, Version = GetInstalledDpInfo(Options.DistributionFile, Dep, DataBase, WorkspaceDir)
139
140        #
141        # Check Dp depex
142        #
143        CheckDpDepex(Dep, Guid, Version, WorkspaceDir)
144
145        #
146        # remove distribution
147        #
148        RemoveDist(Guid, Version, StoredDistFile, DataBase, WorkspaceDir, Options.Yes)
149
150        Logger.Quiet(ST.MSG_FINISH)
151
152        ReturnCode = 0
153
154    except FatalError as XExcept:
155        ReturnCode = XExcept.args[0]
156        if Logger.GetLevel() <= Logger.DEBUG_9:
157            Logger.Quiet(ST.MSG_PYTHON_ON % (python_version(), platform) + \
158                         format_exc())
159    except KeyboardInterrupt:
160        ReturnCode = ABORT_ERROR
161        if Logger.GetLevel() <= Logger.DEBUG_9:
162            Logger.Quiet(ST.MSG_PYTHON_ON % (python_version(), platform) + \
163                         format_exc())
164    except:
165        Logger.Error(
166                    "\nRmPkg",
167                    CODE_ERROR,
168                    ST.ERR_UNKNOWN_FATAL_REMOVING_ERR,
169                    ExtraData=ST.MSG_SEARCH_FOR_HELP % ST.MSG_EDKII_MAIL_ADDR,
170                    RaiseError=False
171                    )
172        Logger.Quiet(ST.MSG_PYTHON_ON % (python_version(), platform) + \
173                     format_exc())
174        ReturnCode = CODE_ERROR
175    return ReturnCode
176
177## GetInstalledDpInfo method
178#
179# Get the installed distribution information
180#
181# @param  DistributionFile: the name of the distribution
182# @param  Dep: the instance of DependencyRules
183# @param  DataBase: the internal database
184# @param  WorkspaceDir: work space directory
185# @retval StoredDistFile: the distribution file that backed up
186# @retval Guid: the Guid of the distribution
187# @retval Version: the Version of distribution
188#
189def GetInstalledDpInfo(DistributionFile, Dep, DataBase, WorkspaceDir):
190    (Guid, Version, NewDpFileName) = DataBase.GetDpByName(os.path.split(DistributionFile)[1])
191    if not Guid:
192        Logger.Error("RmPkg", UNKNOWN_ERROR, ST.ERR_PACKAGE_NOT_INSTALLED % DistributionFile)
193
194    #
195    # Check Dp existing
196    #
197    if not Dep.CheckDpExists(Guid, Version):
198        Logger.Error("RmPkg", UNKNOWN_ERROR, ST.ERR_DISTRIBUTION_NOT_INSTALLED)
199    #
200    # Check for Distribution files existence in /conf/upt, if not exist,
201    # Warn user and go on.
202    #
203    StoredDistFile = os.path.normpath(os.path.join(WorkspaceDir, GlobalData.gUPT_DIR, NewDpFileName))
204    if not os.path.isfile(StoredDistFile):
205        Logger.Warn("RmPkg", ST.WRN_DIST_NOT_FOUND%StoredDistFile)
206        StoredDistFile = None
207
208    return StoredDistFile, Guid, Version
209
210## RemoveDist method
211#
212# remove a distribution
213#
214# @param  Guid: the Guid of the distribution
215# @param  Version: the Version of distribution
216# @param  StoredDistFile: the distribution file that backed up
217# @param  DataBase: the internal database
218# @param  WorkspaceDir: work space directory
219# @param  ForceRemove: whether user want to remove file even it is modified
220#
221def RemoveDist(Guid, Version, StoredDistFile, DataBase, WorkspaceDir, ForceRemove):
222    #
223    # Get Current File List
224    #
225    NewFileList = GetCurrentFileList(DataBase, Guid, Version, WorkspaceDir)
226
227    #
228    # Remove all files
229    #
230    MissingFileList = []
231    for (Path, Md5Sum) in DataBase.GetDpFileList(Guid, Version):
232        if os.path.isfile(Path):
233            if Path in NewFileList:
234                NewFileList.remove(Path)
235            if not ForceRemove:
236                #
237                # check whether modified by users
238                #
239                Md5Signature = md5(open(str(Path), 'rb').read())
240                if Md5Sum != Md5Signature.hexdigest():
241                    Logger.Info(ST.MSG_CONFIRM_REMOVE2 % Path)
242                    Input = stdin.readline()
243                    Input = Input.replace('\r', '').replace('\n', '')
244                    if Input.upper() != 'Y':
245                        continue
246            RemovePath(Path)
247        else:
248            MissingFileList.append(Path)
249
250    for Path in NewFileList:
251        if os.path.isfile(Path):
252            if (not ForceRemove) and (not os.path.split(Path)[1].startswith('.')):
253                Logger.Info(ST.MSG_CONFIRM_REMOVE3 % Path)
254                Input = stdin.readline()
255                Input = Input.replace('\r', '').replace('\n', '')
256                if Input.upper() != 'Y':
257                    continue
258            RemovePath(Path)
259
260    #
261    # Remove distribution files in /Conf/.upt
262    #
263    if StoredDistFile is not None:
264        os.remove(StoredDistFile)
265
266    #
267    # update database
268    #
269    Logger.Quiet(ST.MSG_UPDATE_PACKAGE_DATABASE)
270    DataBase.RemoveDpObj(Guid, Version)
271