Haraka 3.1.2 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.prettierignore +2 -0
  2. package/CONTRIBUTORS.md +24 -2
  3. package/Changes.md +48 -0
  4. package/Plugins.md +81 -64
  5. package/README.md +1 -1
  6. package/bin/haraka +9 -7
  7. package/config/connection.ini +10 -0
  8. package/config/smtp.ini +0 -9
  9. package/connection.js +15 -19
  10. package/docs/CoreConfig.md +2 -3
  11. package/docs/Plugins.md +1 -1
  12. package/docs/plugins/aliases.md +0 -2
  13. package/docs/plugins/queue/qmail-queue.md +0 -1
  14. package/docs/tutorials/Migrating_from_v1_to_v2.md +1 -1
  15. package/logger.js +2 -2
  16. package/outbound/client_pool.js +1 -1
  17. package/outbound/hmail.js +76 -83
  18. package/outbound/index.js +36 -34
  19. package/outbound/queue.js +231 -176
  20. package/package.json +29 -31
  21. package/plugins/prevent_credential_leaks.js +2 -2
  22. package/plugins/process_title.js +1 -1
  23. package/plugins/queue/smtp_forward.js +1 -1
  24. package/plugins/status.js +8 -5
  25. package/plugins/tls.js +1 -1
  26. package/plugins.js +19 -14
  27. package/rfc1869.js +10 -10
  28. package/run_tests +20 -2
  29. package/server.js +15 -10
  30. package/smtp_client.js +2 -9
  31. package/test/config/tls/haraka.local.pem +47 -47
  32. package/test/connection.js +286 -147
  33. package/test/fixtures/line_socket.js +1 -0
  34. package/test/fixtures/util_hmailitem.js +1 -1
  35. package/test/outbound/bounce_net_errors.js +176 -0
  36. package/test/outbound/bounce_rfc3464.js +303 -0
  37. package/test/outbound/hmail.js +140 -104
  38. package/test/outbound/index.js +61 -101
  39. package/test/outbound/qfile.js +25 -25
  40. package/test/outbound/queue.js +233 -0
  41. package/test/plugins/queue/smtp_forward.js +1 -1
  42. package/test/plugins/record_envelope_addresses.js +93 -0
  43. package/test/plugins/tls.js +2 -2
  44. package/test/plugins/xclient.js +137 -0
  45. package/test/rfc1869.js +43 -0
  46. package/test/smtp_client.js +6 -6
  47. package/test/transaction.js +486 -201
  48. package/tls_socket.js +3 -3
  49. package/transaction.js +33 -10
  50. package/config/me +0 -1
  51. package/config/rabbitmq.ini +0 -10
  52. package/config/rabbitmq_amqplib.ini +0 -19
  53. package/config/tls_cert.pem +0 -23
  54. package/config/tls_key.pem +0 -28
  55. package/docs/plugins/queue/rabbitmq.md +0 -34
  56. package/docs/plugins/queue/rabbitmq_amqplib.md +0 -51
  57. package/plugins/queue/rabbitmq.js +0 -141
  58. package/plugins/queue/rabbitmq_amqplib.js +0 -96
  59. package/test/config/tls/ec.pem +0 -23
  60. package/test/config/tls/mismatched.pem +0 -49
  61. package/test/outbound_bounce_net_errors.js +0 -157
  62. package/test/outbound_bounce_rfc3464.js +0 -366
  63. package/test/queue/multibyte +0 -0
  64. package/test/queue/plain +0 -0
  65. package/test/test-queue/delete-me +0 -0
  66. package/test/tls_socket.js +0 -273
package/outbound/queue.js CHANGED
@@ -1,10 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const child_process = require('node:child_process')
4
- const fs = require('node:fs')
4
+ const fs = require('node:fs/promises')
5
5
  const path = require('node:path')
6
6
 
7
- const async = require('async')
8
7
  const { Address } = require('address-rfc2821')
9
8
  const config = require('haraka-config')
10
9
 
@@ -15,37 +14,92 @@ const obc = require('./config')
15
14
  const _qfile = require('./qfile')
16
15
  const obtls = require('./tls')
17
16
 
17
+ class Queue {
18
+ constructor(worker) {
19
+ this.worker = worker
20
+ this.tasks = []
21
+ this.running = 0
22
+ this._scheduled = false
23
+ }
24
+
25
+ push(task) {
26
+ this.tasks.push(task)
27
+ this._schedule()
28
+ }
29
+
30
+ length() {
31
+ return this.tasks.length + this.running
32
+ }
33
+
34
+ _schedule() {
35
+ if (this._scheduled) return
36
+ this._scheduled = true
37
+ setImmediate(() => {
38
+ this._scheduled = false
39
+ this._process()
40
+ })
41
+ }
42
+
43
+ _process() {
44
+ while (this.running < obc.cfg.concurrency_max && this.tasks.length > 0) {
45
+ this.running++
46
+
47
+ this.worker(this.tasks.shift())
48
+ .catch((err) => {
49
+ logger.error(exports, `Queue worker error: ${err}`)
50
+ })
51
+ .finally(() => {
52
+ this.running--
53
+ this._schedule()
54
+ })
55
+ }
56
+ }
57
+ }
58
+
18
59
  exports.name = 'outbound/queue'
19
60
 
20
- let queue_dir
21
61
  if (config.get('queue_dir')) {
22
- queue_dir = path.resolve(config.get('queue_dir'))
62
+ exports.queue_dir = path.resolve(config.get('queue_dir'))
23
63
  } else if (process.env.HARAKA) {
24
- queue_dir = path.resolve(process.env.HARAKA, 'queue')
64
+ exports.queue_dir = path.resolve(process.env.HARAKA, 'queue')
25
65
  } else {
26
- queue_dir = path.resolve('test', 'test-queue')
66
+ exports.queue_dir = path.resolve('test', 'test-queue')
27
67
  }
28
68
 
29
- exports.queue_dir = queue_dir
30
-
31
- const load_queue = async.queue((file, cb) => {
32
- const hmail = new HMailItem(file, path.join(queue_dir, file))
69
+ const load_queue = new Queue(async (file) => {
70
+ const hmail = new HMailItem(file, path.join(exports.queue_dir, file))
33
71
  exports._add_hmail(hmail)
34
- hmail.once('ready', cb)
35
- }, obc.cfg.concurrency_max)
72
+ await new Promise((resolve, reject) => {
73
+ const onReady = () => {
74
+ hmail.off('error', onError)
75
+ resolve()
76
+ }
77
+ const onError = (err) => {
78
+ hmail.off('ready', onReady)
79
+ reject(err)
80
+ }
81
+ hmail.once('ready', onReady)
82
+ hmail.once('error', onError)
83
+ })
84
+ })
36
85
 
37
86
  let in_progress = 0
38
- const delivery_queue = (exports.delivery_queue = async.queue((hmail, cb) => {
87
+ const delivery_queue = (exports.delivery_queue = new Queue(async (hmail) => {
39
88
  in_progress++
40
- hmail.next_cb = () => {
41
- in_progress--
42
- cb()
43
- }
44
- if (obtls.cfg) return hmail.send()
45
- obtls.init(() => {
46
- hmail.send()
89
+ await new Promise((resolve) => {
90
+ hmail.next_cb = () => {
91
+ in_progress--
92
+ resolve()
93
+ }
94
+ if (obtls.cfg) {
95
+ hmail.send()
96
+ } else {
97
+ obtls.init(() => {
98
+ hmail.send()
99
+ })
100
+ }
47
101
  })
48
- }, obc.cfg.concurrency_max))
102
+ }))
49
103
 
50
104
  const temp_fail_queue = (exports.temp_fail_queue = new TimerQueue())
51
105
 
@@ -53,51 +107,48 @@ let queue_count = 0
53
107
 
54
108
  exports.get_stats = () => `${in_progress}/${exports.delivery_queue.length()}/${exports.temp_fail_queue.length()}`
55
109
 
56
- exports.list_queue = (cb) => {
57
- exports._load_cur_queue(null, exports._list_file, cb)
110
+ exports.list_queue = async () => {
111
+ return exports._load_cur_queue(null, exports._list_file)
58
112
  }
59
113
 
60
- exports._stat_file = (file, cb) => {
114
+ exports._stat_file = async (file) => {
61
115
  queue_count++
62
- setImmediate(cb)
63
116
  }
64
117
 
65
- exports.stat_queue = (cb) => {
66
- const self = exports
67
- exports._load_cur_queue(null, exports._stat_file, (err) => {
68
- if (err) return cb(err)
69
- return cb(null, self.stats())
70
- })
118
+ exports.stat_queue = async () => {
119
+ await exports._load_cur_queue(null, exports._stat_file)
120
+ return exports.stats()
71
121
  }
72
122
 
73
- exports.load_queue = (pid) => {
123
+ exports.init_queue = async (pid) => {
74
124
  // Initialise and load queue
75
125
  // This function is called first when not running under cluster,
76
- exports.ensure_queue_dir()
77
- exports.delete_dot_files()
126
+ await exports.ensure_queue_dir()
127
+ await exports.delete_dot_files()
78
128
 
79
- exports._load_cur_queue(pid, exports._add_file, () => {
80
- logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`)
81
- logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`)
82
- logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`)
83
- })
129
+ await exports._load_cur_queue(pid, exports._add_file)
130
+ logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`)
131
+ logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`)
132
+ logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`)
84
133
  }
85
134
 
86
- exports._load_cur_queue = (pid, iteratee, cb) => {
87
- logger.info(exports, 'Loading outbound queue from ', queue_dir)
88
- fs.readdir(queue_dir, (err, files) => {
89
- if (err) {
90
- return logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`)
91
- }
135
+ exports._load_cur_queue = async (pid, iteratee) => {
136
+ logger.info(exports, 'Loading outbound queue from ', exports.queue_dir)
137
+ let files
138
+ try {
139
+ files = await fs.readdir(exports.queue_dir)
140
+ } catch (err) {
141
+ logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
142
+ throw err
143
+ }
92
144
 
93
- this.cur_time = new Date() // set once so we're not calling it a lot
145
+ exports.cur_time = new Date() // set once so we're not calling it a lot
94
146
 
95
- this.load_queue_files(pid, files, iteratee, cb)
96
- })
147
+ return await exports.load_queue_files(pid, files, iteratee)
97
148
  }
98
149
 
99
150
  exports.read_parts = (file) => {
100
- if (file.indexOf(_qfile.platformDOT) === 0) {
151
+ if (file.startsWith(_qfile.platformDOT)) {
101
152
  logger.warn(exports, `'Skipping' dot-file in queue folder: ${file}`)
102
153
  return false
103
154
  }
@@ -116,7 +167,7 @@ exports.read_parts = (file) => {
116
167
  return parts
117
168
  }
118
169
 
119
- exports.rename_to_actual_pid = (file, parts, cb) => {
170
+ exports.rename_to_actual_pid = async (file, parts) => {
120
171
  // maintain some original details for the rename
121
172
  const new_filename = _qfile.name({
122
173
  arrival: parts.arrival,
@@ -125,34 +176,30 @@ exports.rename_to_actual_pid = (file, parts, cb) => {
125
176
  attempts: parts.attempts,
126
177
  })
127
178
 
128
- fs.rename(path.join(queue_dir, file), path.join(queue_dir, new_filename), (err) => {
129
- if (err) {
130
- return cb(`Unable to rename queue file: ${file} to ${new_filename} : ${err}`)
131
- }
132
-
133
- cb(null, new_filename)
134
- })
179
+ try {
180
+ await fs.rename(path.join(exports.queue_dir, file), path.join(exports.queue_dir, new_filename))
181
+ return new_filename
182
+ } catch (err) {
183
+ throw new Error(`Unable to rename queue file: ${file} to ${new_filename} : ${err}`)
184
+ }
135
185
  }
136
186
 
137
- exports._add_file = (file, cb) => {
138
- const self = exports
187
+ exports._add_file = async (file) => {
139
188
  const parts = _qfile.parts(file)
140
189
 
141
- if (parts.next_attempt <= self.cur_time) {
190
+ if (parts.next_attempt <= exports.cur_time) {
142
191
  logger.debug(exports, `File ${file} needs processing now`)
143
192
  load_queue.push(file)
144
193
  } else {
145
- logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - self.cur_time}ms`)
146
- temp_fail_queue.add(file, parts.next_attempt - self.cur_time, () => {
194
+ logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - exports.cur_time}ms`)
195
+ temp_fail_queue.add(file, parts.next_attempt - exports.cur_time, () => {
147
196
  load_queue.push(file)
148
197
  })
149
198
  }
150
-
151
- cb()
199
+ return file
152
200
  }
153
201
 
154
- exports.load_queue_files = (pid, input_files, iteratee, callback = function () {}) => {
155
- const self = exports
202
+ exports.load_queue_files = async (pid, input_files, iteratee) => {
156
203
  const searchPid = parseInt(pid)
157
204
 
158
205
  let stat_renamed = 0
@@ -164,130 +211,134 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
164
211
  logger.info(exports, 'Loading the queue...')
165
212
  }
166
213
 
167
- async.map(
168
- input_files,
169
- (file, cb) => {
170
- const parts = self.read_parts(file)
171
- if (!parts) return cb()
214
+ const results = await Promise.all(
215
+ input_files.map(async (file) => {
216
+ const parts = exports.read_parts(file)
217
+ if (!parts) return null
172
218
 
173
- if (searchPid) {
174
- if (parts.pid !== searchPid) return cb()
219
+ if (!searchPid) {
220
+ stat_loaded++
221
+ return file
222
+ }
175
223
 
176
- self.rename_to_actual_pid(file, parts, (error, renamed_file) => {
177
- if (error) {
178
- logger.error(exports, `${error}`)
179
- return cb()
180
- }
224
+ if (parts.pid !== searchPid) return null
181
225
 
182
- stat_renamed++
183
- stat_loaded++
184
- cb(null, renamed_file)
185
- })
186
- } else {
226
+ try {
227
+ const renamed_file = await exports.rename_to_actual_pid(file, parts)
228
+ stat_renamed++
187
229
  stat_loaded++
188
- cb(null, file)
230
+ return renamed_file
231
+ } catch (error) {
232
+ logger.error(exports, `${error.message}`)
233
+ return null
189
234
  }
190
- },
191
- (err, results) => {
192
- if (err) logger.err(exports, `[pid: ${pid}] ${err}`)
193
- if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`)
194
- logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`)
195
-
196
- async.map(
197
- results.filter((i) => i),
198
- iteratee,
199
- callback,
200
- )
201
- },
235
+ }),
202
236
  )
237
+
238
+ if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`)
239
+ logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`)
240
+
241
+ const iterateeResults = await Promise.all(results.filter((i) => i).map(async (item) => await iteratee(item)))
242
+
243
+ return iterateeResults.filter((result) => result !== null && result !== undefined)
203
244
  }
204
245
 
205
246
  exports.stats = () => {
206
247
  return {
207
- queue_dir,
248
+ queue_dir: exports.queue_dir,
208
249
  queue_count,
209
250
  }
210
251
  }
211
252
 
212
- exports._list_file = (file, cb) => {
213
- const tl_reader = fs.createReadStream(path.join(queue_dir, file), {
214
- start: 0,
215
- end: 3,
216
- })
217
- tl_reader.on('error', (err) => {
218
- console.error(`Error reading queue file: ${file}:`, err)
219
- })
220
- tl_reader.once('data', (buf) => {
221
- // I'm making the assumption here we won't ever read less than 4 bytes
222
- // as no filesystem on the planet should be that dumb...
223
- tl_reader.destroy()
253
+ // position `position`. Loops to handle partial reads.
254
+ // Read exactly `length` bytes into `buffer` starting at `offset`, from file
255
+ async function readFull(handle, buffer, offset, length, position) {
256
+ let totalRead = 0
257
+ while (totalRead < length) {
258
+ const { bytesRead } = await handle.read(buffer, offset + totalRead, length - totalRead, position + totalRead)
259
+ if (bytesRead === 0) {
260
+ throw new Error(`Unexpected end of file: read ${totalRead} of ${length} bytes`)
261
+ }
262
+ totalRead += bytesRead
263
+ }
264
+ }
265
+
266
+ exports._list_file = async (file) => {
267
+ let handle
268
+ try {
269
+ const filePath = path.join(exports.queue_dir, file)
270
+
271
+ handle = await fs.open(filePath, 'r')
272
+
273
+ // Read first 4 bytes to get the todo length
274
+ const buf = Buffer.alloc(4)
275
+ await readFull(handle, buf, 0, 4, 0)
224
276
  const todo_len = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]
225
- const td_reader = fs.createReadStream(path.join(queue_dir, file), {
226
- encoding: 'utf8',
227
- start: 4,
228
- end: todo_len + 3,
229
- })
230
- let todo = ''
231
- td_reader.on('data', (str) => {
232
- todo += str
233
- if (Buffer.byteLength(todo) === todo_len) {
234
- // we read everything
235
- const todo_struct = JSON.parse(todo)
236
- todo_struct.rcpt_to = todo_struct.rcpt_to.map((a) => new Address(a))
237
- todo_struct.mail_from = new Address(todo_struct.mail_from)
238
- todo_struct.file = file
239
- todo_struct.full_path = path.join(queue_dir, file)
240
- const parts = _qfile.parts(file)
241
- todo_struct.pid = parts?.pid || null
242
- cb(null, todo_struct)
243
- }
244
- })
245
- td_reader.on('end', () => {
246
- if (Buffer.byteLength(todo) !== todo_len) {
247
- console.error("Didn't find right amount of data in todo for file:", file)
248
- return cb()
249
- }
250
- })
251
- })
277
+
278
+ const todoBuf = Buffer.alloc(todo_len)
279
+ await readFull(handle, todoBuf, 0, todo_len, 4)
280
+
281
+ const todo = todoBuf.toString('utf8')
282
+ const todo_struct = JSON.parse(todo)
283
+ todo_struct.rcpt_to = todo_struct.rcpt_to.map((a) => new Address(a))
284
+ todo_struct.mail_from = new Address(todo_struct.mail_from)
285
+ todo_struct.file = file
286
+ todo_struct.full_path = filePath
287
+ const parts = _qfile.parts(file)
288
+ todo_struct.pid = parts?.pid || null
289
+ return todo_struct
290
+ } catch (err) {
291
+ console.error(`Error reading queue file: ${file}:`, err)
292
+ return null
293
+ } finally {
294
+ if (handle)
295
+ await handle.close().catch((err) => console.error(`Failed to close queue file handle for ${file}:`, err))
296
+ }
252
297
  }
253
298
 
254
- exports.flush_queue = (domain, pid) => {
299
+ exports.flush_queue = async (domain, pid) => {
255
300
  if (domain) {
256
- exports.list_queue((err, qlist) => {
257
- if (err) return logger.error(exports, `Failed to load queue: ${err}`)
301
+ try {
302
+ const qlist = await exports.list_queue()
258
303
  for (const todo of qlist) {
259
- if (todo.domain.toLowerCase() != domain.toLowerCase()) return
260
- if (pid && todo.pid != pid) return
261
- // console.log("requeue: ", todo);
304
+ if (todo.domain.toLowerCase() !== domain.toLowerCase()) continue
305
+ if (pid && todo.pid !== pid) continue
262
306
  delivery_queue.push(new HMailItem(todo.file, todo.full_path))
263
307
  }
264
- })
308
+ } catch (err) {
309
+ logger.error(exports, `Failed to load queue: ${err.message}`)
310
+ }
265
311
  } else {
266
312
  temp_fail_queue.drain()
267
313
  }
268
314
  }
269
315
 
270
- exports.load_pid_queue = (pid) => {
316
+ exports.load_pid_queue = async (pid) => {
271
317
  logger.info(exports, `Loading queue for pid: ${pid}`)
272
- exports.load_queue(pid)
318
+ await exports.init_queue(pid)
273
319
  }
274
320
 
275
- exports.ensure_queue_dir = () => {
321
+ exports.ensure_queue_dir = async () => {
276
322
  // this code is only run at start-up.
277
- if (fs.existsSync(queue_dir)) return
323
+ try {
324
+ await fs.access(exports.queue_dir)
325
+ return // directory already exists
326
+ } catch (ignore) {
327
+ // directory doesn't exist, try to create it
328
+ }
278
329
 
279
- logger.debug(exports, `Creating queue directory ${queue_dir}`)
330
+ logger.debug(exports, `Creating queue directory ${exports.queue_dir}`)
280
331
  try {
281
- fs.mkdirSync(queue_dir, 493) // 493 == 0755
332
+ await fs.mkdir(exports.queue_dir, { mode: 493 }) // 493 == 0755
282
333
  const cfg = config.get('smtp.ini')
283
334
  let uid
284
335
  let gid
285
- if (cfg.user) uid = child_process.execSync(`id -u ${cfg.user}`).toString().trim()
286
- if (cfg.group) gid = child_process.execSync(`id -g ${cfg.group}`).toString().trim()
336
+ if (cfg.user) uid = parseInt(child_process.execSync(`id -u ${cfg.user}`).toString().trim(), 10)
337
+ if (cfg.group) gid = parseInt(child_process.execSync(`id -g ${cfg.group}`).toString().trim(), 10)
287
338
  if (uid && gid) {
288
- fs.chown(queue_dir, uid, gid)
339
+ await fs.chown(exports.queue_dir, uid, gid)
289
340
  } else if (uid) {
290
- fs.chown(queue_dir, uid)
341
+ await fs.chown(exports.queue_dir, uid, -1)
291
342
  }
292
343
  } catch (err) {
293
344
  if (err.code !== 'EEXIST') {
@@ -297,17 +348,22 @@ exports.ensure_queue_dir = () => {
297
348
  }
298
349
  }
299
350
 
300
- exports.delete_dot_files = () => {
301
- for (const file of fs.readdirSync(queue_dir)) {
302
- if (file.indexOf(_qfile.platformDOT) === 0) {
303
- logger.warn(exports, `Removing left over dot-file: ${file}`)
304
- return fs.unlinkSync(path.join(queue_dir, file))
351
+ exports.delete_dot_files = async () => {
352
+ try {
353
+ const files = await fs.readdir(exports.queue_dir)
354
+ for (const file of files) {
355
+ if (file.startsWith(_qfile.platformDOT)) {
356
+ logger.warn(exports, `Removing left over dot-file: ${file}`)
357
+ await fs.unlink(path.join(exports.queue_dir, file))
358
+ }
305
359
  }
360
+ } catch (err) {
361
+ logger.error(exports, `Error deleting dot files: ${err}`)
306
362
  }
307
363
  }
308
364
 
309
365
  exports._add_hmail = (hmail) => {
310
- if (hmail.next_process < exports.cur_time) {
366
+ if (hmail.next_process <= exports.cur_time) {
311
367
  delivery_queue.push(hmail)
312
368
  } else {
313
369
  temp_fail_queue.add(hmail.filename, hmail.next_process - exports.cur_time, () => {
@@ -316,26 +372,25 @@ exports._add_hmail = (hmail) => {
316
372
  }
317
373
  }
318
374
 
319
- exports.scan_queue_pids = (cb) => {
320
- const self = exports
321
-
375
+ exports.scan_queue_pids = async () => {
322
376
  // Under cluster, this is called first by the master
323
- self.ensure_queue_dir()
324
- self.delete_dot_files()
377
+ await exports.ensure_queue_dir()
378
+ await exports.delete_dot_files()
325
379
 
326
- fs.readdir(queue_dir, (err, files) => {
327
- if (err) {
328
- logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`)
329
- return cb(err)
330
- }
380
+ let files
381
+ try {
382
+ files = await fs.readdir(exports.queue_dir)
383
+ } catch (err) {
384
+ logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
385
+ throw err
386
+ }
331
387
 
332
- const pids = {}
388
+ const pids = {}
333
389
 
334
- for (const file of files) {
335
- const parts = self.read_parts(file)
336
- if (parts) pids[parts.pid] = true
337
- }
390
+ for (const file of files) {
391
+ const parts = exports.read_parts(file)
392
+ if (parts) pids[parts.pid] = true
393
+ }
338
394
 
339
- return cb(null, Object.keys(pids))
340
- })
395
+ return Object.keys(pids)
341
396
  }