Subversion Repositories public

Rev

Rev 100 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
45 luk 1
 
2
/// inotify C++ interface implementation
3
/**
4
 * \file inotify-cxx.cpp
5
 *
6
 * inotify C++ interface
7
 *
108 luk 8
 * Copyright (C) 2006, 2007, 2008, 2012 Lukas Jelinek <lukas@aiken.cz>
45 luk 9
 *
10
 * This program is free software; you can redistribute it and/or
11
 * modify it under the terms of one of the following licenses:
12
 *
13
 * \li 1. X11-style license (see LICENSE-X11)
14
 * \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL)
15
 * \li 3. GNU General Public License, version 2  (see LICENSE-GPL)
16
 *
17
 * If you want to help with choosing the best license for you,
18
 * please visit http://www.gnu.org/licenses/license-list.html.
19
 *
108 luk 20
 * Credits:
21
 *   Christian Ruppert (new include to build with GCC 4.4+)
22
 *
45 luk 23
 */
24
 
25
 
26
#include <errno.h>
27
#include <unistd.h>
47 luk 28
#include <fcntl.h>
108 luk 29
#include <cstdio>
45 luk 30
 
31
#include "inotify-cxx.h"
32
 
63 luk 33
/// procfs inotify base path
34
#define PROCFS_INOTIFY_BASE "/proc/sys/fs/inotify/"
35
 
47 luk 36
/// dump separator (between particular entries)
45 luk 37
#define DUMP_SEP \
38
  ({ \
39
    if (!rStr.empty()) { \
40
      rStr.append(","); \
41
    } \
42
  })
43
 
47 luk 44
 
55 luk 45
 
47 luk 46
int32_t InotifyEvent::GetDescriptor() const
47
{
48
  return  m_pWatch != NULL            // if watch exists
49
      ?   m_pWatch->GetDescriptor()   // return its descriptor
50
      :   -1;                         // else return -1
51
}
52
 
45 luk 53
uint32_t InotifyEvent::GetMaskByName(const std::string& rName)
54
{
55
  if (rName == "IN_ACCESS")
56
    return IN_ACCESS;
57
  else if (rName == "IN_MODIFY")
58
    return IN_MODIFY;
59
  else if (rName == "IN_ATTRIB")
60
    return IN_ATTRIB;
61
  else if (rName == "IN_CLOSE_WRITE")
62
    return IN_CLOSE_WRITE;
63
  else if (rName == "IN_CLOSE_NOWRITE")
64
    return IN_CLOSE_NOWRITE;
55 luk 65
  else if (rName == "IN_OPEN")
66
    return IN_OPEN;
45 luk 67
  else if (rName == "IN_MOVED_FROM")
68
    return IN_MOVED_FROM;
69
  else if (rName == "IN_MOVED_TO")
70
    return IN_MOVED_TO;
71
  else if (rName == "IN_CREATE")
72
    return IN_CREATE;
73
  else if (rName == "IN_DELETE")
74
    return IN_DELETE;
75
  else if (rName == "IN_DELETE_SELF")
76
    return IN_DELETE_SELF;
77
  else if (rName == "IN_UNMOUNT")
78
    return IN_UNMOUNT;
79
  else if (rName == "IN_Q_OVERFLOW")
80
    return IN_Q_OVERFLOW;
81
  else if (rName == "IN_IGNORED")
82
    return IN_IGNORED;
83
  else if (rName == "IN_CLOSE")
84
    return IN_CLOSE;
85
  else if (rName == "IN_MOVE")
86
    return IN_MOVE;
87
  else if (rName == "IN_ISDIR")
88
    return IN_ISDIR;
89
  else if (rName == "IN_ONESHOT")
90
    return IN_ONESHOT;
91
  else if (rName == "IN_ALL_EVENTS")
92
    return IN_ALL_EVENTS;
93
 
49 luk 94
#ifdef IN_DONT_FOLLOW
95
  else if (rName == "IN_DONT_FOLLOW")
96
    return IN_DONT_FOLLOW;
97
#endif // IN_DONT_FOLLOW
98
 
99
#ifdef IN_ONLYDIR
100
  else if (rName == "IN_ONLYDIR")
101
    return IN_ONLYDIR;
102
#endif // IN_ONLYDIR
63 luk 103
 
104
#ifdef IN_MOVE_SELF
105
  else if (rName == "IN_MOVE_SELF")
106
    return IN_MOVE_SELF;
107
#endif // IN_MOVE_SELF
49 luk 108
 
45 luk 109
  return (uint32_t) 0;
110
}
111
 
112
void InotifyEvent::DumpTypes(uint32_t uValue, std::string& rStr)
113
{
114
  rStr = "";
115
 
116
  if (IsType(uValue, IN_ALL_EVENTS)) {
117
    rStr.append("IN_ALL_EVENTS");
118
  }
119
  else {
120
    if (IsType(uValue, IN_ACCESS)) {
121
      DUMP_SEP;
122
      rStr.append("IN_ACCESS");    
123
    }
124
    if (IsType(uValue, IN_MODIFY)) {
125
      DUMP_SEP;
126
      rStr.append("IN_MODIFY");
127
    }
128
    if (IsType(uValue, IN_ATTRIB)) {
129
      DUMP_SEP;
130
      rStr.append("IN_ATTRIB");
131
    }
132
    if (IsType(uValue, IN_CREATE)) {
133
      DUMP_SEP;
134
      rStr.append("IN_CREATE");
135
    }
136
    if (IsType(uValue, IN_DELETE)) {
137
      DUMP_SEP;
138
      rStr.append("IN_DELETE");
139
    }
140
    if (IsType(uValue, IN_DELETE_SELF)) {
141
      DUMP_SEP;
142
      rStr.append("IN_DELETE_SELF");
143
    }
144
    if (IsType(uValue, IN_OPEN)) {
145
      DUMP_SEP;
146
      rStr.append("IN_OPEN");
147
    }
148
    if (IsType(uValue, IN_CLOSE)) {
149
      DUMP_SEP;
150
      rStr.append("IN_CLOSE");
151
    }
63 luk 152
 
153
#ifdef IN_MOVE_SELF
154
    if (IsType(uValue, IN_MOVE_SELF)) {
155
      DUMP_SEP;
156
      rStr.append("IN_MOVE_SELF");    
157
    }
158
#endif // IN_MOVE_SELF
159
 
45 luk 160
    else {
161
      if (IsType(uValue, IN_CLOSE_WRITE)) {
162
        DUMP_SEP;
163
        rStr.append("IN_CLOSE_WRITE");
164
      }
165
      if (IsType(uValue, IN_CLOSE_NOWRITE)) {
166
        DUMP_SEP;
167
        rStr.append("IN_CLOSE_NOWRITE");
168
      }
169
    }
170
    if (IsType(uValue, IN_MOVE)) {
171
      DUMP_SEP;
172
      rStr.append("IN_MOVE");
173
    }
174
    else {
175
      if (IsType(uValue, IN_MOVED_FROM)) {
176
        DUMP_SEP;
177
        rStr.append("IN_MOVED_FROM");
178
      }
179
      if (IsType(uValue, IN_MOVED_TO)) {
180
        DUMP_SEP;
181
        rStr.append("IN_MOVED_TO");
182
      }
183
    }
184
  }
185
  if (IsType(uValue, IN_UNMOUNT)) {
186
    DUMP_SEP;
187
    rStr.append("IN_UNMOUNT");
188
  }
189
  if (IsType(uValue, IN_Q_OVERFLOW)) {
190
    DUMP_SEP;
191
    rStr.append("IN_Q_OVERFLOW");
192
  }
193
  if (IsType(uValue, IN_IGNORED)) {
194
    DUMP_SEP;
195
    rStr.append("IN_IGNORED");
196
  }
197
  if (IsType(uValue, IN_ISDIR)) {
198
    DUMP_SEP;
199
    rStr.append("IN_ISDIR");
200
  }
201
  if (IsType(uValue, IN_ONESHOT)) {
202
    DUMP_SEP;
203
    rStr.append("IN_ONESHOT");
204
  }
49 luk 205
 
206
#ifdef IN_DONT_FOLLOW
207
  if (IsType(uValue, IN_DONT_FOLLOW)) {
208
    DUMP_SEP;
209
    rStr.append("IN_DONT_FOLLOW");
210
  }
211
#endif // IN_DONT_FOLLOW
212
 
213
#ifdef IN_ONLYDIR
214
  if (IsType(uValue, IN_ONLYDIR)) {
215
    DUMP_SEP;
216
    rStr.append("IN_ONLYDIR");
217
  }
218
#endif // IN_ONLYDIR
45 luk 219
}
220
 
221
void InotifyEvent::DumpTypes(std::string& rStr) const
222
{
47 luk 223
  DumpTypes(m_uMask, rStr);
45 luk 224
}
225
 
226
 
49 luk 227
void InotifyWatch::SetMask(uint32_t uMask) throw (InotifyException)
228
{
51 luk 229
  IN_WRITE_BEGIN
230
 
49 luk 231
  if (m_wd != -1) {
232
    int wd = inotify_add_watch(m_pInotify->GetDescriptor(), m_path.c_str(), uMask);
51 luk 233
    if (wd != m_wd) {
234
      IN_WRITE_END_NOTHROW
49 luk 235
      throw InotifyException(IN_EXC_MSG("changing mask failed"), wd == -1 ? errno : EINVAL, this);
51 luk 236
    }
49 luk 237
  }
238
 
239
  m_uMask = uMask;
51 luk 240
 
241
  IN_WRITE_END
49 luk 242
}
243
 
244
void InotifyWatch::SetEnabled(bool fEnabled) throw (InotifyException)
245
{
51 luk 246
  IN_WRITE_BEGIN
247
 
248
  if (fEnabled == m_fEnabled) {
249
    IN_WRITE_END_NOTHROW
49 luk 250
    return;
51 luk 251
  }
49 luk 252
 
253
  if (m_pInotify != NULL) {
254
    if (fEnabled) {
255
      m_wd = inotify_add_watch(m_pInotify->GetDescriptor(), m_path.c_str(), m_uMask);
51 luk 256
      if (m_wd == -1) {
257
        IN_WRITE_END_NOTHROW
49 luk 258
        throw InotifyException(IN_EXC_MSG("enabling watch failed"), errno, this);
51 luk 259
      }
49 luk 260
      m_pInotify->m_watches.insert(IN_WATCH_MAP::value_type(m_wd, this));
261
    }
262
    else {
51 luk 263
      if (inotify_rm_watch(m_pInotify->GetDescriptor(), m_wd) != 0) {
264
        IN_WRITE_END_NOTHROW
49 luk 265
        throw InotifyException(IN_EXC_MSG("disabling watch failed"), errno, this);
51 luk 266
      }
49 luk 267
      m_pInotify->m_watches.erase(m_wd);
268
      m_wd = -1;
269
    }
270
  }
271
 
272
  m_fEnabled = fEnabled;
51 luk 273
 
274
  IN_WRITE_END
49 luk 275
}
276
 
65 luk 277
void InotifyWatch::__Disable()
63 luk 278
{
279
  IN_WRITE_BEGIN
280
 
281
  if (!m_fEnabled) {
282
    IN_WRITE_END_NOTHROW
283
    throw InotifyException(IN_EXC_MSG("event cannot occur on disabled watch"), EINVAL, this);
284
  }
285
 
286
  if (m_pInotify != NULL) {
287
    m_pInotify->m_watches.erase(m_wd);
288
    m_wd = -1;
289
  }
290
 
291
  m_fEnabled = false;
292
 
293
  IN_WRITE_END
294
}
49 luk 295
 
63 luk 296
 
47 luk 297
Inotify::Inotify() throw (InotifyException)
45 luk 298
{
51 luk 299
  IN_LOCK_INIT
300
 
47 luk 301
  m_fd = inotify_init();
51 luk 302
  if (m_fd == -1) {
303
    IN_LOCK_DONE
49 luk 304
    throw InotifyException(IN_EXC_MSG("inotify init failed"), errno, NULL);
51 luk 305
  }
45 luk 306
}
307
 
308
Inotify::~Inotify()
309
{
310
  Close();
51 luk 311
 
312
  IN_LOCK_DONE
45 luk 313
}
314
 
315
void Inotify::Close()
316
{
51 luk 317
  IN_WRITE_BEGIN
318
 
45 luk 319
  if (m_fd != -1) {
320
    RemoveAll();
321
    close(m_fd);
322
    m_fd = -1;
323
  }
51 luk 324
 
325
  IN_WRITE_END
45 luk 326
}
327
 
47 luk 328
void Inotify::Add(InotifyWatch* pWatch) throw (InotifyException)
45 luk 329
{
51 luk 330
  IN_WRITE_BEGIN
331
 
49 luk 332
  // invalid descriptor - this case shouldn't occur - go away
51 luk 333
  if (m_fd == -1) {
334
    IN_WRITE_END_NOTHROW
47 luk 335
    throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
51 luk 336
  }
49 luk 337
 
338
  // this path already watched - go away  
51 luk 339
  if (FindWatch(pWatch->GetPath()) != NULL) {
340
    IN_WRITE_END_NOTHROW
49 luk 341
    throw InotifyException(IN_EXC_MSG("path already watched"), EBUSY, this);
51 luk 342
  }
49 luk 343
 
344
  // for enabled watch
345
  if (pWatch->IsEnabled()) {
45 luk 346
 
49 luk 347
    // try to add watch to kernel
348
    int wd = inotify_add_watch(m_fd, pWatch->GetPath().c_str(), pWatch->GetMask());
349
 
350
    // adding failed - go away
51 luk 351
    if (wd == -1) {
352
      IN_WRITE_END_NOTHROW
49 luk 353
      throw InotifyException(IN_EXC_MSG("adding watch failed"), errno, this);
51 luk 354
    }
49 luk 355
 
356
    // this path already watched (but defined another way)
357
    InotifyWatch* pW = FindWatch(wd);
358
    if (pW != NULL) {
359
 
360
      // try to recover old watch because it may be modified - then go away
361
      if (inotify_add_watch(m_fd, pW->GetPath().c_str(), pW->GetMask()) < 0) {
51 luk 362
        IN_WRITE_END_NOTHROW
49 luk 363
        throw InotifyException(IN_EXC_MSG("watch collision detected and recovery failed"), errno, this);
364
      }
365
      else {
366
        // recovery failed - go away
51 luk 367
        IN_WRITE_END_NOTHROW
49 luk 368
        throw InotifyException(IN_EXC_MSG("path already watched (but defined another way)"), EBUSY, this);
369
      }
370
    }
371
 
372
    pWatch->m_wd = wd;
373
    m_watches.insert(IN_WATCH_MAP::value_type(pWatch->m_wd, pWatch));
374
  }
375
 
376
  m_paths.insert(IN_WP_MAP::value_type(pWatch->m_path, pWatch));
47 luk 377
  pWatch->m_pInotify = this;
51 luk 378
 
379
  IN_WRITE_END
45 luk 380
}
381
 
47 luk 382
void Inotify::Remove(InotifyWatch* pWatch) throw (InotifyException)
45 luk 383
{
51 luk 384
  IN_WRITE_BEGIN
385
 
49 luk 386
  // invalid descriptor - this case shouldn't occur - go away
51 luk 387
  if (m_fd == -1) {
388
    IN_WRITE_END_NOTHROW
47 luk 389
    throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
51 luk 390
  }
49 luk 391
 
392
  // for enabled watch
393
  if (pWatch->m_wd != -1) {  
45 luk 394
 
49 luk 395
    // removing watch failed - go away
51 luk 396
    if (inotify_rm_watch(m_fd, pWatch->m_wd) == -1) {
397
      IN_WRITE_END_NOTHROW
49 luk 398
      throw InotifyException(IN_EXC_MSG("removing watch failed"), errno, this);
51 luk 399
    }
49 luk 400
    m_watches.erase(pWatch->m_wd);
401
    pWatch->m_wd = -1;
402
  }
403
 
404
  m_paths.erase(pWatch->m_path);
47 luk 405
  pWatch->m_pInotify = NULL;
51 luk 406
 
407
  IN_WRITE_END
45 luk 408
}
409
 
410
void Inotify::RemoveAll()
411
{
51 luk 412
  IN_WRITE_BEGIN
413
 
49 luk 414
  IN_WP_MAP::iterator it = m_paths.begin();
415
  while (it != m_paths.end()) {
45 luk 416
    InotifyWatch* pW = (*it).second;
49 luk 417
    if (pW->m_wd != -1) {
418
      inotify_rm_watch(m_fd, pW->m_wd);
419
      pW->m_wd = -1;
420
    }
45 luk 421
    pW->m_pInotify = NULL;
422
    it++;
423
  }
424
 
425
  m_watches.clear();
49 luk 426
  m_paths.clear();
51 luk 427
 
428
  IN_WRITE_END
45 luk 429
}
430
 
47 luk 431
void Inotify::WaitForEvents(bool fNoIntr) throw (InotifyException)
45 luk 432
{
433
  ssize_t len = 0;
434
 
435
  do {
436
    len = read(m_fd, m_buf, INOTIFY_BUFLEN);
437
  } while (fNoIntr && len == -1 && errno == EINTR);
438
 
61 luk 439
  if (len == -1 && !(errno == EWOULDBLOCK || errno == EINTR))
440
    throw InotifyException(IN_EXC_MSG("reading events failed"), errno, this);
441
 
442
  if (len == -1)
47 luk 443
    return;
45 luk 444
 
51 luk 445
  IN_WRITE_BEGIN
446
 
45 luk 447
  ssize_t i = 0;
448
  while (i < len) {
47 luk 449
    struct inotify_event* pEvt = (struct inotify_event*) &m_buf[i];
450
    InotifyWatch* pW = FindWatch(pEvt->wd);
49 luk 451
    if (pW != NULL) {
47 luk 452
      InotifyEvent evt(pEvt, pW);
65 luk 453
      if (    InotifyEvent::IsType(pW->GetMask(), IN_ONESHOT)
454
          ||  InotifyEvent::IsType(evt.GetMask(), IN_IGNORED))
455
        pW->__Disable();
47 luk 456
      m_events.push_back(evt);
457
    }
458
    i += INOTIFY_EVENT_SIZE + (ssize_t) pEvt->len;
45 luk 459
  }
460
 
51 luk 461
  IN_WRITE_END
45 luk 462
}
463
 
47 luk 464
bool Inotify::GetEvent(InotifyEvent* pEvt) throw (InotifyException)
45 luk 465
{
51 luk 466
  if (pEvt == NULL)
467
    throw InotifyException(IN_EXC_MSG("null pointer to event"), EINVAL, this);
47 luk 468
 
51 luk 469
  IN_WRITE_BEGIN
470
 
471
  bool b = !m_events.empty();
472
  if (b) {
473
    *pEvt = m_events.front();
45 luk 474
    m_events.pop_front();
51 luk 475
  }
476
 
477
  IN_WRITE_END
47 luk 478
 
45 luk 479
  return b;
480
}
481
 
47 luk 482
bool Inotify::PeekEvent(InotifyEvent* pEvt) throw (InotifyException)
45 luk 483
{
47 luk 484
  if (pEvt == NULL)
485
    throw InotifyException(IN_EXC_MSG("null pointer to event"), EINVAL, this);
45 luk 486
 
51 luk 487
  IN_READ_BEGIN
488
 
489
  bool b = !m_events.empty();
490
  if (b) {
47 luk 491
    *pEvt = m_events.front();
492
  }
493
 
51 luk 494
  IN_READ_END
495
 
496
  return b;
45 luk 497
}
498
 
499
InotifyWatch* Inotify::FindWatch(int iDescriptor)
500
{
51 luk 501
  IN_READ_BEGIN
502
 
45 luk 503
  IN_WATCH_MAP::iterator it = m_watches.find(iDescriptor);
51 luk 504
  InotifyWatch* pW = it == m_watches.end() ? NULL : (*it).second;
505
 
506
  IN_READ_END
507
 
508
  return pW;
45 luk 509
}
49 luk 510
 
511
InotifyWatch* Inotify::FindWatch(const std::string& rPath)
512
{
51 luk 513
  IN_READ_BEGIN
514
 
49 luk 515
  IN_WP_MAP::iterator it = m_paths.find(rPath);
51 luk 516
  InotifyWatch* pW = it == m_paths.end() ? NULL : (*it).second;
517
 
518
  IN_READ_END
49 luk 519
 
51 luk 520
  return pW;
49 luk 521
}
45 luk 522
 
47 luk 523
void Inotify::SetNonBlock(bool fNonBlock) throw (InotifyException)
524
{
51 luk 525
  IN_WRITE_BEGIN
526
 
527
  if (m_fd == -1) {
528
    IN_WRITE_END_NOTHROW
47 luk 529
    throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
51 luk 530
  }
47 luk 531
 
532
  int res = fcntl(m_fd, F_GETFL);
51 luk 533
  if (res == -1) {
534
    IN_WRITE_END_NOTHROW
47 luk 535
    throw InotifyException(IN_EXC_MSG("cannot get inotify flags"), errno, this);
51 luk 536
  }
47 luk 537
 
538
  if (fNonBlock) {
539
    res |= O_NONBLOCK;
540
  }
541
  else {
542
    res &= ~O_NONBLOCK;
543
  }
544
 
51 luk 545
  if (fcntl(m_fd, F_SETFL, res) == -1) {
546
    IN_WRITE_END_NOTHROW
47 luk 547
    throw InotifyException(IN_EXC_MSG("cannot set inotify flags"), errno, this);
51 luk 548
  }
549
 
550
  IN_WRITE_END
63 luk 551
}
47 luk 552
 
67 luk 553
void Inotify::SetCloseOnExec(bool fClOnEx) throw (InotifyException)
554
{
555
  IN_WRITE_BEGIN
556
 
557
  if (m_fd == -1) {
558
    IN_WRITE_END_NOTHROW
559
    throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
560
  }
561
 
562
  int res = fcntl(m_fd, F_GETFD);
563
  if (res == -1) {
564
    IN_WRITE_END_NOTHROW
565
    throw InotifyException(IN_EXC_MSG("cannot get inotify flags"), errno, this);
566
  }
567
 
568
  if (fClOnEx) {
569
    res |= FD_CLOEXEC;
570
  }
571
  else {
572
    res &= ~FD_CLOEXEC;
573
  }
574
 
575
  if (fcntl(m_fd, F_SETFD, res) == -1) {
576
    IN_WRITE_END_NOTHROW
577
    throw InotifyException(IN_EXC_MSG("cannot set inotify flags"), errno, this);
578
  }
579
 
580
  IN_WRITE_END
581
}
582
 
63 luk 583
uint32_t Inotify::GetCapability(InotifyCapability_t cap) throw (InotifyException)
584
{
585
  FILE* f = fopen(GetCapabilityPath(cap).c_str(), "r");
586
  if (f == NULL)
587
    throw InotifyException(IN_EXC_MSG("cannot get capability"), errno, NULL);
588
 
589
  unsigned int val = 0;
590
  if (fscanf(f, "%u", &val) != 1) {
591
    fclose(f);
592
    throw InotifyException(IN_EXC_MSG("cannot get capability"), EIO, NULL);
593
  }
594
 
595
  fclose(f);
596
 
597
  return (uint32_t) val;
598
}
599
 
600
void Inotify::SetCapability(InotifyCapability_t cap, uint32_t val) throw (InotifyException)
601
{
602
  FILE* f = fopen(GetCapabilityPath(cap).c_str(), "w");
603
  if (f == NULL)
604
    throw InotifyException(IN_EXC_MSG("cannot set capability"), errno, NULL);
605
 
606
  if (fprintf(f, "%u", (unsigned int) val) <= 0) {
607
    fclose(f);
608
    throw InotifyException(IN_EXC_MSG("cannot set capability"), EIO, NULL);
609
  }
610
 
611
  fclose(f);
612
}
613
 
614
std::string Inotify::GetCapabilityPath(InotifyCapability_t cap) throw (InotifyException)
615
{
616
  std::string path(PROCFS_INOTIFY_BASE);
617
 
618
  switch (cap) {
619
    case IN_MAX_EVENTS:
620
      path.append("max_queued_events");
621
      break;
622
    case IN_MAX_INSTANCES:
623
      path.append("max_user_instances");
624
      break;
625
    case IN_MAX_WATCHES:
626
      path.append("max_user_watches");
627
      break;
628
    default:
629
      throw InotifyException(IN_EXC_MSG("unknown capability type"), EINVAL, NULL);
630
  }
631
 
632
  return path;
633
}
634