1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2023 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import optparse
17import os
18import sys
19import json
20import stat
21
22sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
23    os.pardir, os.pardir, os.pardir, "build"))
24from scripts.util import build_utils  # noqa: E402
25
26#default json
27
28APP_SANDBOX_DEFAULT = '''
29{
30    "common" : [{
31        "top-sandbox-switch": "ON",
32        "app-base" : [{
33            "sandbox-root" : "/mnt/sandbox/<currentUserId>/<PackageName>",
34            "sandbox-ns-flags" : [],
35            "mount-paths" : [],
36            "symbol-links": [],
37            "flags-point" : []
38        }],
39        "app-resources" : [{
40            "sandbox-root" : "/mnt/sandbox/<currentUserId>/<PackageName>",
41            "mount-paths" : [],
42            "flags-point" : [],
43            "symbol-links" : []
44        }]
45    }],
46    "individual" : [{}],
47    "permission" :[{}]
48}
49'''
50#only string in list
51
52
53def _merge_list(origin, new):
54    if origin is None or new is None:
55        return
56    for data1 in new:
57        if data1 not in origin:
58            origin.append(data1)
59
60
61def _is_same_data(data1, data2, keys):
62    for key in keys:
63        if data1.get(key) != data2.get(key):
64            return False
65    return True
66
67#for object in list
68
69
70def _handle_same_array(data1, data2):
71    for field in ["sandbox-root", "sandbox-path", "check-action-status", "fs-type", "link-name"]:
72        if data1.get(field) is not None:
73            data2[field] = data1[field]
74
75    for field in ["sandbox-flags"]: # by list merger
76        item = data1.get(field)
77        if item is not None and len(item) > 0:
78            _merge_list(data2[field], item)
79
80
81def _merge_scope_array(origin, new, keys):
82    for data1 in new:
83        found = False
84        for data2 in origin:
85            if _is_same_data(data1, data2, keys):
86                found = True
87                _handle_same_array(data1, data2)
88                break
89        if not found:
90            origin.append(data1)
91
92
93def _handle_same_data(data1, data2, field_infos):
94    for field in ["sandbox-root"]:
95        if data1.get(field) is not None:
96            data2[field] = data1[field]
97
98    # for array
99    for name, keys in field_infos.items():
100        item = data1.get(name)
101        if item is not None and len(item) > 0:
102            _merge_scope_array(data2[field], item, keys)
103
104
105def _merge_scope_flags_point(origin, new):
106    field_infos = {
107        "mount-paths": ["src-path"]
108    }
109    for data1 in new:
110        found = False
111        for data2 in origin:
112            if _is_same_data(data1, data2, ["flags"]):
113                found = True
114                _handle_same_data(data1, data2, field_infos)
115                break
116
117        if not found:
118            origin.append(data1)
119
120
121def _merge_scope_app(origin, new):
122    field_infos = {
123        "mount-paths": ["src-path"],
124        "symbol-links": ["target-name"]
125    }
126    # normal filed
127    for k in ["sandbox-root", "sandbox-switch", "gids", "sandbox-ns-flags"]:
128        if new[0].get(k) is not None:
129            origin[0][k] = new[0].get(k)
130
131    # for flags-point
132    flags_points = new[0].get("flags-point")
133    if flags_points:
134        _merge_scope_flags_point(origin[0]["flags-point"], flags_points)
135
136    # by list merger
137    for field in ["sandbox-ns-flags"]:
138        item = origin[0].get(field)
139        if item is not None and len(item) > 0:
140            _merge_list(new[0][field], item)
141
142    # for array
143    for name, keys in field_infos.items():
144        item = new[0].get(name)
145        if item is not None and len(item) > 0:
146            _merge_scope_array(origin[0].get(name), item, keys)
147
148
149def _merge_scope_individual(origin, new):
150    for k, v in new.items():
151        if k not in origin:
152            origin[k] = v
153        else:
154            _merge_scope_app(origin[k], v)
155
156
157def _merge_scope_permission(origin, new):
158    for k, v in new.items():
159        if k not in origin:
160            origin[k] = v
161        else:
162            _merge_scope_app(origin[k], v)
163
164
165def _merge_scope_common(origin, new):
166    # 处理 top-sandbox-switch
167    for name in ["top-sandbox-switch"]:
168        if new.get(name) :
169            origin[name] = new.get(name)
170
171    #处理 app-base
172    app = new.get("app-base")
173    if app is not None and len(app) > 0:
174        _merge_scope_app(origin.get("app-base"), app)
175        pass
176
177    #处理 app-resources
178    app = new.get("app-resources")
179    if app is not None and len(app) > 0:
180        _merge_scope_app(origin.get("app-resources"), app)
181        pass
182
183
184def parse_args(args):
185    args = build_utils.expand_file_args(args)
186    parser = optparse.OptionParser()
187    build_utils.add_depfile_option(parser)
188    parser.add_option('--output', help='fixed sandbox configure file')
189    parser.add_option('--source-file', help='source para file')
190    parser.add_option('--patterns', action="append",
191        type="string", dest="patterns", help='replace string patterns like libpath:lib64')
192    parser.add_option('--extra_sandbox_cfg', action="append",
193        type="string", dest="extra_sandbox_cfgs", help='extra sandbox')
194
195    options, _ = parser.parse_args(args)
196    return options
197
198
199def __substitude_contents(options, source_file):
200    with open(source_file, "r") as f:
201        contents = f.read()
202        if not options.patterns:
203            return json.loads(contents)
204        for pattern in options.patterns:
205            parts = pattern.split(":")
206            contents = contents.replace("{%s}" % parts[0], parts[1])
207        return json.loads(contents)
208
209
210def _get_json_list(options):
211    data_list = []
212    #decode source file
213    contents = __substitude_contents(options, options.source_file)
214    if contents :
215        data_list.append(contents)
216
217    if options.extra_sandbox_cfgs is None:
218        return data_list
219
220    #decode extra file
221    for sandbox_cfg in options.extra_sandbox_cfgs:
222        contents = __substitude_contents(options, sandbox_cfg)
223        if contents :
224            data_list.append(contents)
225    return data_list
226
227
228def fix_sandbox_config_file(options):
229    data_list = _get_json_list(options)
230    #decode template
231    origin_json = json.loads(APP_SANDBOX_DEFAULT)
232
233    for data in data_list:
234        # 处理common
235        common = data.get("common")
236        if common is not None and len(common) > 0:
237            _merge_scope_common(origin_json.get("common")[0], common[0])
238
239        #处理individual
240        individuals = data.get("individual")
241        if individuals is not None and len(individuals) > 0:
242            _merge_scope_individual(origin_json.get("individual")[0], individuals[0])
243            pass
244
245        # 处理permission
246        permission = data.get("permission")
247        if permission is not None and len(permission) > 0:
248            _merge_scope_permission(origin_json.get("permission")[0], permission[0])
249
250    # dump json to output
251    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
252    modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IWGRP | stat.S_IRGRP
253    with os.fdopen(os.open(options.output, flags, modes), 'w') as f:
254        f.write(json.dumps(origin_json, ensure_ascii=False, indent=2))
255
256
257def main(args):
258    options = parse_args(args)
259    depfile_deps = ([options.source_file])
260    fix_sandbox_config_file(options)
261    build_utils.write_depfile(options.depfile, options.output, depfile_deps, add_pydeps=False)
262
263if __name__ == '__main__':
264    sys.exit(main(sys.argv[1:]))
265