1#
2# Copyright (C) 2018 The Android Open Source Project
3#
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#
16
17import fcntl
18import logging
19import os
20
21from host_controller import common
22
23
24class FileLock(object):
25    """Class for using files as a locking mechanism for devices.
26
27    This is for checking whether a certain device is running a job or not when
28    the automated self-update happens.
29
30    Attributes:
31        _devlock_dir: string, represent the home directory of the user.
32        _lock_fd: dict, maps serial number of the devices and file descriptor.
33    """
34
35    def __init__(self, file_name=None, mode=None):
36        """Initializes the file lock managing class instance.
37
38        Args:
39            file_name: string, name of the file to be opened. Existing lock
40                       files will be opened if given as None
41            mode: string, the mode argument to open() function.
42        """
43        self._lock_fd = {}
44        self._devlock_dir = os.path.join(
45            os.path.expanduser("~"), common._DEVLOCK_DIR)
46        if not os.path.exists(self._devlock_dir):
47            os.mkdir(self._devlock_dir)
48
49        if file_name:
50            file_list = [file_name]
51        else:
52            file_list = [file_name for file_name in os.listdir(self._devlock_dir)]
53        logging.debug("file_list for file lock: %s", file_list)
54
55        for file_name in file_list:
56            if os.path.isfile(os.path.join(self._devlock_dir, file_name)):
57                self._OpenFile(file_name, mode)
58
59    def _OpenFile(self, serial, mode=None):
60        """Opens the given lock file and store the file descriptor to _lock_fd.
61
62        Args:
63            serial: string, serial number of a device.
64            mode: string, the mode argument to open() function.
65                  Set to "w+" if given as None.
66        """
67        if serial in self._lock_fd and self._lock_fd[serial]:
68            logging.info("Lock for the device %s already exists." % serial)
69            return
70
71        try:
72            if not mode:
73                mode = "w+"
74            self._lock_fd[serial] = open(
75                os.path.join(self._devlock_dir, serial), mode, 0)
76        except IOError as e:
77            logging.exception(e)
78            return False
79
80    def LockDevice(self, serial, suppress_lock_warning=False, block=False):
81        """Tries to lock the file corresponding to "serial".
82
83        Args:
84            serial: string, serial number of a device.
85            suppress_lock_warning: bool, True to suppress warning log output.
86            block: bool, True to block at the fcntl.lockf(), waiting for it
87                   to be unlocked.
88
89        Returns:
90            True if successfully acquired the lock. False otherwise.
91        """
92        if serial not in self._lock_fd:
93            ret = self._OpenFile(serial)
94            if ret == False:
95                return ret
96
97        try:
98            operation = fcntl.LOCK_EX
99            if not block:
100                operation |= fcntl.LOCK_NB
101            fcntl.lockf(self._lock_fd[serial], operation)
102        except IOError as e:
103            if not suppress_lock_warning:
104                logging.exception(e)
105            return False
106
107        return True
108
109    def UnlockDevice(self, serial):
110        """Releases the lock file corresponding to "serial".
111
112        Args:
113            serial: string, serial number of a device.
114        """
115        if serial not in self._lock_fd:
116            logging.error("Lock for the device %s does not exist." % serial)
117            return False
118
119        fcntl.lockf(self._lock_fd[serial], fcntl.LOCK_UN)
120