- fixed PathInfo::recursive_rmdir
[platform/upstream/libzypp.git] / zypp / PathInfo.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/PathInfo.cc
10  *
11 */
12
13 #include <sys/types.h> // for ::minor, ::major macros
14
15 #include <iostream>
16 #include <fstream>
17 #include <iomanip>
18
19 #include <boost/filesystem/operations.hpp>
20 #include <boost/filesystem/exception.hpp>
21
22 #include "zypp/base/Logger.h"
23 #include "zypp/base/String.h"
24 #include "zypp/base/IOStream.h"
25
26 #include "zypp/ExternalProgram.h"
27 #include "zypp/PathInfo.h"
28 #include "zypp/Digest.h"
29
30
31 using std::string;
32
33 ///////////////////////////////////////////////////////////////////
34 namespace zypp
35 { /////////////////////////////////////////////////////////////////
36   ///////////////////////////////////////////////////////////////////
37   namespace filesystem
38   { /////////////////////////////////////////////////////////////////
39
40     /******************************************************************
41      **
42      ** FUNCTION NAME : operator<<
43      ** FUNCTION TYPE : std::ostream &
44     */
45     std::ostream & operator<<( std::ostream & str, FileType obj )
46     {
47       switch ( obj ) {
48 #define EMUMOUT(T) case T: return str << #T; break
49         EMUMOUT( FT_NOT_AVAIL );
50         EMUMOUT( FT_NOT_EXIST );
51         EMUMOUT( FT_FILE );
52         EMUMOUT( FT_DIR );
53         EMUMOUT( FT_CHARDEV );
54         EMUMOUT( FT_BLOCKDEV );
55         EMUMOUT( FT_FIFO );
56         EMUMOUT( FT_LINK );
57         EMUMOUT( FT_SOCKET );
58 #undef EMUMOUT
59       }
60       return str;
61     }
62
63     ///////////////////////////////////////////////////////////////////
64     //
65     //  METHOD NAME : StatMode::fileType
66     //  METHOD TYPE : FileType
67     //
68     FileType StatMode::fileType() const
69     {
70       if ( isFile() )
71         return FT_FILE;
72       if ( isDir() )
73         return FT_DIR;
74       if ( isLink() )
75         return FT_LINK;
76       if ( isChr() )
77         return FT_CHARDEV;
78       if ( isBlk() )
79         return FT_BLOCKDEV;
80       if ( isFifo() )
81         return FT_FIFO;
82       if ( isSock() )
83         return FT_SOCKET ;
84
85       return FT_NOT_AVAIL;
86     }
87
88     /******************************************************************
89      **
90      ** FUNCTION NAME : operator<<
91      ** FUNCTION TYPE : std::ostream &
92     */
93     std::ostream & operator<<( std::ostream & str, const StatMode & obj )
94     {
95       iostr::IosFmtFlagsSaver autoResoreState( str );
96
97       char t = '?';
98       if ( obj.isFile() )
99         t = '-';
100       else if ( obj.isDir() )
101         t = 'd';
102       else if ( obj.isLink() )
103         t = 'l';
104       else if ( obj.isChr() )
105         t = 'c';
106       else if ( obj.isBlk() )
107         t = 'b';
108       else if ( obj.isFifo() )
109         t = 'p';
110       else if ( obj.isSock() )
111         t = 's';
112
113       str << t << " " << std::setfill( '0' ) << std::setw( 4 ) << std::oct << obj.perm();
114       return str;
115     }
116
117     ///////////////////////////////////////////////////////////////////
118     //
119     //  Class : PathInfo
120     //
121     ///////////////////////////////////////////////////////////////////
122
123     ///////////////////////////////////////////////////////////////////
124     //
125     //  METHOD NAME : PathInfo::PathInfo
126     //  METHOD TYPE : Constructor
127     //
128     PathInfo::PathInfo()
129     : mode_e( STAT )
130     , error_i( -1 )
131     {}
132
133     ///////////////////////////////////////////////////////////////////
134     //
135     //  METHOD NAME : PathInfo::PathInfo
136     //  METHOD TYPE : Constructor
137     //
138     PathInfo::PathInfo( const Pathname & path, Mode initial )
139     : path_t( path )
140     , mode_e( initial )
141     , error_i( -1 )
142     {
143       operator()();
144     }
145
146     ///////////////////////////////////////////////////////////////////
147     //
148     //  METHOD NAME : PathInfo::PathInfo
149     //  METHOD TYPE : Constructor
150     //
151     PathInfo::PathInfo( const std::string & path, Mode initial )
152     : path_t( path )
153     , mode_e( initial )
154     , error_i( -1 )
155     {
156       operator()();
157     }
158
159     ///////////////////////////////////////////////////////////////////
160     //
161     //  METHOD NAME : PathInfo::PathInfo
162     //  METHOD TYPE : Constructor
163     //
164     PathInfo::PathInfo( const char * path, Mode initial )
165     : path_t( path )
166     , mode_e( initial )
167     , error_i( -1 )
168     {
169       operator()();
170     }
171
172     ///////////////////////////////////////////////////////////////////
173     //
174     //  METHOD NAME : PathInfo::~PathInfo
175     //  METHOD TYPE : Destructor
176     //
177     PathInfo::~PathInfo()
178     {
179     }
180
181     ///////////////////////////////////////////////////////////////////
182     //
183     //  METHOD NAME : PathInfo::operator()
184     //  METHOD TYPE : bool
185     //
186     bool PathInfo::operator()()
187     {
188       if ( path_t.empty() ) {
189         error_i = -1;
190       } else {
191         switch ( mode_e ) {
192         case STAT:
193           error_i = ::stat( path_t.asString().c_str(), &statbuf_C );
194           break;
195         case LSTAT:
196           error_i = ::lstat( path_t.asString().c_str(), &statbuf_C );
197           break;
198         }
199         if ( error_i == -1 )
200           error_i = errno;
201       }
202       return !error_i;
203     }
204
205     ///////////////////////////////////////////////////////////////////
206     //
207     //  METHOD NAME : PathInfo::fileType
208     //  METHOD TYPE : File_type
209     //
210     FileType PathInfo::fileType() const
211     {
212       if ( isExist() )
213         return asStatMode().fileType();
214       return FT_NOT_EXIST;
215     }
216
217     ///////////////////////////////////////////////////////////////////
218     //
219     //  METHOD NAME : PathInfo::userMay
220     //  METHOD TYPE : mode_t
221     //
222     mode_t PathInfo::userMay() const
223     {
224       if ( !isExist() )
225         return 0;
226       if ( owner() == getuid() ) {
227         return( uperm()/0100 );
228       } else if ( group() == getgid() ) {
229         return( gperm()/010 );
230       }
231       return operm();
232     }
233
234     /******************************************************************
235      **
236      ** FUNCTION NAME : PathInfo::major
237      ** FUNCTION TYPE : unsigned int
238      */
239     unsigned int PathInfo::major() const
240     {
241       return isBlk() || isChr() ? ::major(statbuf_C.st_rdev) : 0;
242     }
243
244     /******************************************************************
245      **
246      ** FUNCTION NAME : PathInfo::minor
247      ** FUNCTION TYPE : unsigned int
248      */
249     unsigned int PathInfo::minor() const
250     {
251       return isBlk() || isChr() ? ::minor(statbuf_C.st_rdev) : 0;
252     }
253
254     /******************************************************************
255      **
256      ** FUNCTION NAME : operator<<
257      ** FUNCTION TYPE :  std::ostream &
258     */
259     std::ostream & operator<<( std::ostream & str, const PathInfo & obj )
260     {
261       iostr::IosFmtFlagsSaver autoResoreState( str );
262
263       str << obj.asString() << "{";
264       if ( !obj.isExist() ) {
265         str << "does not exist}";
266       } else {
267         str << obj.asStatMode() << " " << std::dec << obj.owner() << "/" << obj.group();
268
269         if ( obj.isFile() )
270           str << " size " << obj.size();
271
272         str << "}";
273       }
274
275       return str;
276     }
277
278     ///////////////////////////////////////////////////////////////////
279     //
280     //  filesystem utilities
281     //
282     ///////////////////////////////////////////////////////////////////
283
284     /******************************************************************
285      **
286      ** FUNCTION NAME : _Log_Result
287      ** FUNCTION TYPE : int
288      **
289      ** DESCRIPTION : Helper function to log return values.
290     */
291     inline int _Log_Result( const int res, const char * rclass = "errno" )
292     {
293       if ( res )
294         DBG << " FAILED: " << rclass << " " << res;
295       DBG << std::endl;
296       return res;
297     }
298
299     ///////////////////////////////////////////////////////////////////
300     //
301     //  METHOD NAME : PathInfo::mkdir
302     //  METHOD TYPE : int
303     //
304     int mkdir( const Pathname & path, unsigned mode )
305     {
306       DBG << "mkdir " << path << ' ' << str::octstring( mode );
307       if ( ::mkdir( path.asString().c_str(), mode ) == -1 ) {
308         return _Log_Result( errno );
309       }
310       return _Log_Result( 0 );
311     }
312
313     ///////////////////////////////////////////////////////////////////
314     //
315     //  METHOD NAME : assert_dir()
316     //  METHOD TYPE : int
317     //
318     int assert_dir( const Pathname & path, unsigned mode )
319     {
320       string::size_type pos, lastpos = 0;
321       string spath = path.asString()+"/";
322       int ret = 0;
323
324       if(path.empty())
325         return ENOENT;
326
327       // skip ./
328       if(path.relative())
329         lastpos=2;
330       // skip /
331       else
332         lastpos=1;
333
334       //    DBG << "about to create " << spath << endl;
335       while((pos = spath.find('/',lastpos)) != string::npos )
336         {
337           string dir = spath.substr(0,pos);
338           ret = ::mkdir(dir.c_str(), mode);
339           if(ret == -1)
340             {
341               // ignore errors about already existing directorys
342               if(errno == EEXIST)
343                 ret=0;
344               else
345                 ret=errno;
346             }
347           //    DBG << "creating directory " << dir << (ret?" failed":" succeeded") << endl;
348           lastpos = pos+1;
349         }
350       return ret;
351     }
352
353     ///////////////////////////////////////////////////////////////////
354     //
355     //  METHOD NAME : rmdir
356     //  METHOD TYPE : int
357     //
358     int rmdir( const Pathname & path )
359     {
360       DBG << "rmdir " << path;
361       if ( ::rmdir( path.asString().c_str() ) == -1 ) {
362         return _Log_Result( errno );
363       }
364       return _Log_Result( 0 );
365     }
366
367     ///////////////////////////////////////////////////////////////////
368     //
369     //  METHOD NAME : recursive_rmdir
370     //  METHOD TYPE : int
371     //
372     int recursive_rmdir( const Pathname & path )
373     {
374       DBG << "recursive_rmdir " << path << ' ';
375       PathInfo p( path );
376
377       if ( !p.isExist() ) {
378         return _Log_Result( 0 );
379       }
380
381       if ( !p.isDir() ) {
382         return _Log_Result( ENOTDIR );
383       }
384
385       try
386         {
387           boost::filesystem::path bp( path.asString(), boost::filesystem::native );
388           boost::filesystem::remove_all( bp );
389         }
390       catch ( boost::filesystem::filesystem_error & excpt )
391         {
392           DBG << " FAILED: " << excpt.what() << std::endl;
393           return -1;
394         }
395
396       return _Log_Result( 0 );
397     }
398
399     ///////////////////////////////////////////////////////////////////
400     //
401     //  METHOD NAME : clean_dir
402     //  METHOD TYPE : int
403     //
404     int clean_dir( const Pathname & path )
405     {
406       DBG << "clean_dir " << path << ' ';
407       PathInfo p( path );
408
409       if ( !p.isExist() ) {
410         return _Log_Result( 0 );
411       }
412
413       if ( !p.isDir() ) {
414         return _Log_Result( ENOTDIR );
415       }
416
417       string cmd( str::form( "cd '%s' && rm -rf --preserve-root -- *", path.asString().c_str() ) );
418       ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
419       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
420         DBG << "  " << output;
421       }
422       int ret = prog.close();
423       return _Log_Result( ret, "returned" );
424     }
425
426     ///////////////////////////////////////////////////////////////////
427     //
428     //  METHOD NAME : copy_dir
429     //  METHOD TYPE : int
430     //
431     int copy_dir( const Pathname & srcpath, const Pathname & destpath )
432     {
433       DBG << "copy_dir " << srcpath << " -> " << destpath << ' ';
434
435       PathInfo sp( srcpath );
436       if ( !sp.isDir() ) {
437         return _Log_Result( ENOTDIR );
438       }
439
440       PathInfo dp( destpath );
441       if ( !dp.isDir() ) {
442         return _Log_Result( ENOTDIR );
443       }
444
445       PathInfo tp( destpath + srcpath.basename() );
446       if ( tp.isExist() ) {
447         return _Log_Result( EEXIST );
448       }
449
450
451       const char *const argv[] = {
452         "/bin/cp",
453         "-dR",
454         "--",
455         srcpath.asString().c_str(),
456         destpath.asString().c_str(),
457         NULL
458       };
459       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
460       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
461         DBG << "  " << output;
462       }
463       int ret = prog.close();
464       return _Log_Result( ret, "returned" );
465     }
466
467     ///////////////////////////////////////////////////////////////////
468     //
469     //  METHOD NAME : copy_dir_content
470     //  METHOD TYPE : int
471     //
472     int copy_dir_content(const Pathname & srcpath, const Pathname & destpath)
473     {
474       DBG << "copy_dir " << srcpath << " -> " << destpath << ' ';
475
476       PathInfo sp( srcpath );
477       if ( !sp.isDir() ) {
478         return _Log_Result( ENOTDIR );
479       }
480
481       PathInfo dp( destpath );
482       if ( !dp.isDir() ) {
483         return _Log_Result( ENOTDIR );
484       }
485
486       if ( srcpath == destpath ) {
487         return _Log_Result( EEXIST );
488       }
489
490       std::string src( srcpath.asString());
491       src += "/.";
492       const char *const argv[] = {
493         "/bin/cp",
494         "-dR",
495         "--",
496         src.c_str(),
497         destpath.asString().c_str(),
498         NULL
499       };
500       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
501       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
502         DBG << "  " << output;
503       }
504       int ret = prog.close();
505       return _Log_Result( ret, "returned" );
506     }
507
508     ///////////////////////////////////////////////////////////////////
509     //
510     //  METHOD NAME : readdir
511     //  METHOD TYPE : int
512     //
513     int readdir( std::list<std::string> & retlist,
514                  const Pathname & path, bool dots )
515     {
516       retlist.clear();
517
518       DBG << "readdir " << path << ' ';
519
520       DIR * dir = ::opendir( path.asString().c_str() );
521       if ( ! dir ) {
522         return _Log_Result( errno );
523       }
524
525       struct dirent *entry;
526       while ( (entry = ::readdir( dir )) != 0 ) {
527
528         if ( entry->d_name[0] == '.' ) {
529           if ( !dots )
530             continue;
531           if ( entry->d_name[1] == '\0'
532                || (    entry->d_name[1] == '.'
533                     && entry->d_name[2] == '\0' ) )
534             continue;
535         }
536         retlist.push_back( entry->d_name );
537       }
538
539       ::closedir( dir );
540
541       return _Log_Result( 0 );
542     }
543
544
545     ///////////////////////////////////////////////////////////////////
546     //
547     //  METHOD NAME : readdir
548     //  METHOD TYPE : int
549     //
550     int readdir( std::list<Pathname> & retlist,
551                  const Pathname & path, bool dots )
552     {
553       retlist.clear();
554
555       std::list<string> content;
556       int res = readdir( content, path, dots );
557
558       if ( !res ) {
559         for ( std::list<string>::const_iterator it = content.begin(); it != content.end(); ++it ) {
560           retlist.push_back( path + *it );
561         }
562       }
563
564       return res;
565     }
566
567     ///////////////////////////////////////////////////////////////////
568     //
569     //  METHOD NAME : readdir
570     //  METHOD TYPE : int
571     //
572     int readdir( DirContent & retlist, const Pathname & path,
573                  bool dots, PathInfo::Mode statmode )
574     {
575       retlist.clear();
576
577       std::list<string> content;
578       int res = readdir( content, path, dots );
579
580       if ( !res ) {
581         for ( std::list<string>::const_iterator it = content.begin(); it != content.end(); ++it ) {
582           PathInfo p( path + *it, statmode );
583           retlist.push_back( DirEntry( *it, p.fileType() ) );
584         }
585       }
586
587       return res;
588     }
589
590     ///////////////////////////////////////////////////////////////////
591     //
592     //  METHOD NAME : is_empty_dir
593     //  METHOD TYPE : int
594     //
595     int is_empty_dir(const Pathname & path)
596     {
597       DIR * dir = ::opendir( path.asString().c_str() );
598       if ( ! dir ) {
599         return _Log_Result( errno );
600       }
601
602       struct dirent *entry;
603       while ( (entry = ::readdir( dir )) != NULL )
604       {
605         std::string name(entry->d_name);
606
607         if ( name == "." || name == "..")
608           continue;
609
610         break;
611       }
612       ::closedir( dir );
613
614       return entry != NULL ? -1 : 0;
615     }
616
617     ///////////////////////////////////////////////////////////////////
618     //
619     //  METHOD NAME : unlink
620     //  METHOD TYPE : int
621     //
622     int unlink( const Pathname & path )
623     {
624       DBG << "unlink " << path;
625       if ( ::unlink( path.asString().c_str() ) == -1 ) {
626         return _Log_Result( errno );
627       }
628       return _Log_Result( 0 );
629     }
630
631     ///////////////////////////////////////////////////////////////////
632     //
633     //  METHOD NAME : rename
634     //  METHOD TYPE : int
635     //
636     int rename( const Pathname & oldpath, const Pathname & newpath )
637     {
638       DBG << "rename " << oldpath << " -> " << newpath;
639       if ( ::rename( oldpath.asString().c_str(), newpath.asString().c_str() ) == -1 ) {
640         return _Log_Result( errno );
641       }
642       return _Log_Result( 0 );
643     }
644
645     ///////////////////////////////////////////////////////////////////
646     //
647     //  METHOD NAME : copy
648     //  METHOD TYPE : int
649     //
650     int copy( const Pathname & file, const Pathname & dest )
651     {
652       DBG << "copy " << file << " -> " << dest << ' ';
653
654       PathInfo sp( file );
655       if ( !sp.isFile() ) {
656         return _Log_Result( EINVAL );
657       }
658
659       PathInfo dp( dest );
660       if ( dp.isDir() ) {
661         return _Log_Result( EISDIR );
662       }
663
664       const char *const argv[] = {
665         "/bin/cp",
666         "--",
667         file.asString().c_str(),
668         dest.asString().c_str(),
669         NULL
670       };
671       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
672       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
673         DBG << "  " << output;
674       }
675       int ret = prog.close();
676       return _Log_Result( ret, "returned" );
677     }
678
679     ///////////////////////////////////////////////////////////////////
680     //
681     //  METHOD NAME : symlink
682     //  METHOD TYPE : int
683     //
684     int symlink( const Pathname & oldpath, const Pathname & newpath )
685     {
686       DBG << "symlink " << newpath << " -> " << oldpath;
687       if ( ::symlink( oldpath.asString().c_str(), newpath.asString().c_str() ) == -1 ) {
688         return _Log_Result( errno );
689       }
690       return _Log_Result( 0 );
691     }
692
693     ///////////////////////////////////////////////////////////////////
694     //
695     //  METHOD NAME : hardlink
696     //  METHOD TYPE : int
697     //
698     int hardlink( const Pathname & oldpath, const Pathname & newpath )
699     {
700       DBG << "hardlink " << newpath << " -> " << oldpath;
701       if ( ::link( oldpath.asString().c_str(), newpath.asString().c_str() ) == -1 ) {
702         return _Log_Result( errno );
703       }
704       return _Log_Result( 0 );
705     }
706
707     ///////////////////////////////////////////////////////////////////
708     //
709     //  METHOD NAME : copy_file2dir
710     //  METHOD TYPE : int
711     //
712     int copy_file2dir( const Pathname & file, const Pathname & dest )
713     {
714       DBG << "copy_file2dir " << file << " -> " << dest << ' ';
715
716       PathInfo sp( file );
717       if ( !sp.isFile() ) {
718         return _Log_Result( EINVAL );
719       }
720
721       PathInfo dp( dest );
722       if ( !dp.isDir() ) {
723         return _Log_Result( ENOTDIR );
724       }
725
726       const char *const argv[] = {
727         "/bin/cp",
728         "--",
729         file.asString().c_str(),
730         dest.asString().c_str(),
731         NULL
732       };
733       ExternalProgram prog( argv, ExternalProgram::Stderr_To_Stdout );
734       for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
735         DBG << "  " << output;
736       }
737       int ret = prog.close();
738       return _Log_Result( ret, "returned" );
739     }
740
741     ///////////////////////////////////////////////////////////////////
742     //
743     //  METHOD NAME : md5sum
744     //  METHOD TYPE : std::string
745     //
746     std::string md5sum( const Pathname & file )
747     {
748       if ( ! PathInfo( file ).isFile() ) {
749         return string();
750       }
751       std::ifstream istr( file.asString().c_str() );
752       if ( ! istr ) {
753         return string();
754       }
755       return Digest::digest( "MD5", istr );
756     }
757
758     ///////////////////////////////////////////////////////////////////
759     //
760     //  METHOD NAME : sha1sum
761     //  METHOD TYPE : std::string
762     //
763     std::string sha1sum( const Pathname & file )
764     {
765       if ( ! PathInfo( file ).isFile() ) {
766         return string();
767       }
768       std::ifstream istr( file.asString().c_str() );
769       if ( ! istr ) {
770         return string();
771       }
772       return Digest::digest( "SHA1", istr );
773     }
774
775     ///////////////////////////////////////////////////////////////////
776     //
777     //  METHOD NAME : erase
778     //  METHOD TYPE : int
779     //
780     int erase( const Pathname & path )
781     {
782       int res = 0;
783       PathInfo p( path, PathInfo::LSTAT );
784       if ( p.isExist() )
785         {
786           if ( p.isDir() )
787             res = recursive_rmdir( path );
788           else
789             res = unlink( path );
790         }
791       return res;
792     }
793
794     ///////////////////////////////////////////////////////////////////
795     //
796     //  METHOD NAME : chmod
797     //  METHOD TYPE : int
798     //
799     int chmod( const Pathname & path, mode_t mode )
800     {
801       DBG << "chmod " << path << ' ' << str::octstring( mode );
802       if ( ::chmod( path.asString().c_str(), mode ) == -1 ) {
803         return _Log_Result( errno );
804       }
805       return _Log_Result( 0 );
806     }
807
808     ///////////////////////////////////////////////////////////////////
809     //
810     //  METHOD NAME : zipType
811     //  METHOD TYPE : ZIP_TYPE
812     //
813     ZIP_TYPE zipType( const Pathname & file )
814     {
815       ZIP_TYPE ret = ZT_NONE;
816
817       int fd = open( file.asString().c_str(), O_RDONLY );
818
819       if ( fd != -1 ) {
820         const int magicSize = 3;
821         unsigned char magic[magicSize];
822         memset( magic, 0, magicSize );
823         if ( read( fd, magic, magicSize ) == magicSize ) {
824           if ( magic[0] == 0037 && magic[1] == 0213 ) {
825             ret = ZT_GZ;
826           } else if ( magic[0] == 'B' && magic[1] == 'Z' && magic[2] == 'h' ) {
827             ret = ZT_BZ2;
828           }
829         }
830         close( fd );
831       }
832
833       return ret;
834     }
835
836     /////////////////////////////////////////////////////////////////
837   } // namespace filesystem
838   ///////////////////////////////////////////////////////////////////
839   /////////////////////////////////////////////////////////////////
840 } // namespace zypp
841 ///////////////////////////////////////////////////////////////////