6
6
import logging
7
7
import os
8
8
import random
9
+ import subprocess
9
10
10
11
from azure .monitor .opentelemetry .exporter ._utils import PeriodicTask
11
12
12
13
logger = logging .getLogger (__name__ )
13
14
15
+ ICACLS_PATH = os .path .join (
16
+ os .environ .get ("SYSTEMDRIVE" , "C:" ), r"\Windows\System32\icacls.exe"
17
+ )
14
18
15
19
def _fmt (timestamp ):
16
20
return timestamp .strftime ("%Y-%m-%dT%H%M%S.%f" )
@@ -92,19 +96,25 @@ def __init__(
92
96
self ._max_size = max_size
93
97
self ._retention_period = retention_period
94
98
self ._write_timeout = write_timeout
95
- self ._maintenance_routine ()
96
- self ._maintenance_task = PeriodicTask (
97
- interval = maintenance_period ,
98
- function = self ._maintenance_routine ,
99
- name = name ,
100
- )
101
- self ._lease_period = lease_period
102
- self ._maintenance_task .daemon = True
103
- self ._maintenance_task .start ()
99
+
100
+ self ._enabled = self ._check_and_set_folder_permissions ()
101
+ if self ._enabled :
102
+ self ._maintenance_routine ()
103
+ self ._maintenance_task = PeriodicTask (
104
+ interval = maintenance_period ,
105
+ function = self ._maintenance_routine ,
106
+ name = name ,
107
+ )
108
+ self ._lease_period = lease_period
109
+ self ._maintenance_task .daemon = True
110
+ self ._maintenance_task .start ()
111
+ else :
112
+ logger .error ("Could not set secure permissions on storage folder, local storage is disabled." )
104
113
105
114
def close (self ):
106
- self ._maintenance_task .cancel ()
107
- self ._maintenance_task .join ()
115
+ if self ._enabled :
116
+ self ._maintenance_task .cancel ()
117
+ self ._maintenance_task .join ()
108
118
109
119
def __enter__ (self ):
110
120
return self
@@ -121,43 +131,49 @@ def _maintenance_routine(self):
121
131
except Exception :
122
132
pass # keep silent
123
133
134
+ # pylint: disable=too-many-nested-blocks
124
135
def gets (self ):
125
- now = _now ()
126
- lease_deadline = _fmt (now )
127
- retention_deadline = _fmt (now - _seconds (self ._retention_period ))
128
- timeout_deadline = _fmt (now - _seconds (self ._write_timeout ))
129
- try :
130
- for name in sorted (os .listdir (self ._path )):
131
- path = os .path .join (self ._path , name )
132
- if not os .path .isfile (path ):
133
- continue # skip if not a file
134
- if path .endswith (".tmp" ):
135
- if name < timeout_deadline :
136
+ if self ._enabled :
137
+ now = _now ()
138
+ lease_deadline = _fmt (now )
139
+ retention_deadline = _fmt (now - _seconds (self ._retention_period ))
140
+ timeout_deadline = _fmt (now - _seconds (self ._write_timeout ))
141
+ try :
142
+ for name in sorted (os .listdir (self ._path )):
143
+ path = os .path .join (self ._path , name )
144
+ if not os .path .isfile (path ):
145
+ continue # skip if not a file
146
+ if path .endswith (".tmp" ):
147
+ if name < timeout_deadline :
148
+ try :
149
+ os .remove (path ) # TODO: log data loss
150
+ except Exception :
151
+ pass # keep silent
152
+ if path .endswith (".lock" ):
153
+ if path [path .rindex ("@" ) + 1 : - 5 ] > lease_deadline :
154
+ continue # under lease
155
+ new_path = path [: path .rindex ("@" )]
136
156
try :
137
- os .remove (path ) # TODO: log data loss
157
+ os .rename (path , new_path )
138
158
except Exception :
139
159
pass # keep silent
140
- if path .endswith (".lock" ):
141
- if path [path .rindex ("@" ) + 1 : - 5 ] > lease_deadline :
142
- continue # under lease
143
- new_path = path [: path .rindex ("@" )]
144
- try :
145
- os .rename (path , new_path )
146
- except Exception :
147
- pass # keep silent
148
- path = new_path
149
- if path .endswith (".blob" ):
150
- if name < retention_deadline :
151
- try :
152
- os .remove (path ) # TODO: log data loss
153
- except Exception :
154
- pass # keep silent
155
- else :
156
- yield LocalFileBlob (path )
157
- except Exception :
158
- pass # keep silent
160
+ path = new_path
161
+ if path .endswith (".blob" ):
162
+ if name < retention_deadline :
163
+ try :
164
+ os .remove (path ) # TODO: log data loss
165
+ except Exception :
166
+ pass # keep silent
167
+ else :
168
+ yield LocalFileBlob (path )
169
+ except Exception :
170
+ pass # keep silent
171
+ else :
172
+ pass
159
173
160
174
def get (self ):
175
+ if not self ._enabled :
176
+ return None
161
177
cursor = self .gets ()
162
178
try :
163
179
return next (cursor )
@@ -166,12 +182,8 @@ def get(self):
166
182
return None
167
183
168
184
def put (self , data , lease_period = None ):
169
- # Create path if it doesn't exist
170
- try :
171
- if not os .path .isdir (self ._path ):
172
- os .makedirs (self ._path , exist_ok = True )
173
- except Exception :
174
- pass # keep silent
185
+ if not self ._enabled :
186
+ return None
175
187
if not self ._check_storage_size ():
176
188
return None
177
189
blob = LocalFileBlob (
@@ -187,6 +199,44 @@ def put(self, data, lease_period=None):
187
199
lease_period = self ._lease_period
188
200
return blob .put (data , lease_period = lease_period )
189
201
202
+ def _check_and_set_folder_permissions (self ):
203
+ """
204
+ Validate and set folder permissions where the telemetry data will be stored.
205
+ :return: True if folder was created and permissions set successfully, False otherwise.
206
+ :rtype: bool
207
+ """
208
+ try :
209
+ # Create path if it doesn't exist
210
+ os .makedirs (self ._path , exist_ok = True )
211
+ # Windows
212
+ if os .name == "nt" :
213
+ user = self ._get_current_user ()
214
+ if not user :
215
+ logger .warning (
216
+ "Failed to retrieve current user. Skipping folder permission setup."
217
+ )
218
+ return False
219
+ result = subprocess .run (
220
+ [
221
+ ICACLS_PATH ,
222
+ self ._path ,
223
+ "/grant" ,
224
+ "*S-1-5-32-544:(OI)(CI)F" , # Full permission for Administrators
225
+ f"{ user } :(OI)(CI)F" ,
226
+ "/inheritance:r" ,
227
+ ],
228
+ check = False ,
229
+ )
230
+ if result .returncode == 0 :
231
+ return True
232
+ # Unix
233
+ else :
234
+ os .chmod (self ._path , 0o700 )
235
+ return True
236
+ except Exception :
237
+ pass # keep silent
238
+ return False
239
+
190
240
def _check_storage_size (self ):
191
241
size = 0
192
242
# pylint: disable=unused-variable
@@ -209,7 +259,19 @@ def _check_storage_size(self):
209
259
"Persistent storage max capacity has been "
210
260
"reached. Currently at {}KB. Telemetry will be "
211
261
"lost. Please consider increasing the value of "
212
- "'storage_max_size' in exporter config." .format (str (size / 1024 ))
262
+ "'storage_max_size' in exporter config." .format (
263
+ str (size / 1024 )
264
+ )
213
265
)
214
266
return False
215
267
return True
268
+
269
+ def _get_current_user (self ):
270
+ user = ""
271
+ domain = os .environ .get ("USERDOMAIN" )
272
+ username = os .environ .get ("USERNAME" )
273
+ if domain and username :
274
+ user = f"{ domain } \\ { username } "
275
+ else :
276
+ user = os .getlogin ()
277
+ return user
0 commit comments