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