4 // Copyright (c) 2000-2005 Per Liden
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
31 #include <ext/stdio_filebuf.h>
34 #include <sys/types.h>
38 #include <sys/param.h>
44 #include <archive_entry.h>
46 using __gnu_cxx::stdio_filebuf
;
48 pkgutil::pkgutil(const string
& name
)
53 memset(&sa
, 0, sizeof(sa
));
54 sa
.sa_handler
= SIG_IGN
;
55 sigaction(SIGHUP
, &sa
, 0);
56 sigaction(SIGINT
, &sa
, 0);
57 sigaction(SIGQUIT
, &sa
, 0);
58 sigaction(SIGTERM
, &sa
, 0);
61 void pkgutil::db_open(const string
& path
)
64 root
= trim_filename(path
+ "/");
65 const string filename
= root
+ PKG_DB
;
67 int fd
= open(filename
.c_str(), O_RDONLY
);
69 throw runtime_error_with_errno("could not open " + filename
);
71 stdio_filebuf
<char> filebuf(fd
, ios::in
, getpagesize());
74 throw runtime_error_with_errno("could not read " + filename
);
81 getline(in
, info
.version
);
87 break; // End of record
89 info
.files
.insert(info
.files
.end(), file
);
91 if (!info
.files
.empty())
92 packages
[name
] = info
;
96 cerr
<< packages
.size() << " packages found in database" << endl
;
100 void pkgutil::db_commit()
102 const string dbfilename
= root
+ PKG_DB
;
103 const string dbfilename_new
= dbfilename
+ ".incomplete_transaction";
104 const string dbfilename_bak
= dbfilename
+ ".backup";
106 // Remove failed transaction (if it exists)
107 if (unlink(dbfilename_new
.c_str()) == -1 && errno
!= ENOENT
)
108 throw runtime_error_with_errno("could not remove " + dbfilename_new
);
110 // Write new database
111 int fd_new
= creat(dbfilename_new
.c_str(), 0444);
113 throw runtime_error_with_errno("could not create " + dbfilename_new
);
115 stdio_filebuf
<char> filebuf_new(fd_new
, ios::out
, getpagesize());
116 ostream
db_new(&filebuf_new
);
117 for (packages_t::const_iterator i
= packages
.begin(); i
!= packages
.end(); ++i
) {
118 if (!i
->second
.files
.empty()) {
119 db_new
<< i
->first
<< "\n";
120 db_new
<< i
->second
.version
<< "\n";
121 copy(i
->second
.files
.begin(), i
->second
.files
.end(), ostream_iterator
<string
>(db_new
, "\n"));
128 // Make sure the new database was successfully written
130 throw runtime_error("could not write " + dbfilename_new
);
132 // Synchronize file to disk
133 if (fsync(fd_new
) == -1)
134 throw runtime_error_with_errno("could not synchronize " + dbfilename_new
);
136 // Relink database backup
137 if (unlink(dbfilename_bak
.c_str()) == -1 && errno
!= ENOENT
)
138 throw runtime_error_with_errno("could not remove " + dbfilename_bak
);
139 if (link(dbfilename
.c_str(), dbfilename_bak
.c_str()) == -1)
140 throw runtime_error_with_errno("could not create " + dbfilename_bak
);
142 // Move new database into place
143 if (rename(dbfilename_new
.c_str(), dbfilename
.c_str()) == -1)
144 throw runtime_error_with_errno("could not rename " + dbfilename_new
+ " to " + dbfilename
);
147 cerr
<< packages
.size() << " packages written to database" << endl
;
151 void pkgutil::db_add_pkg(const string
& name
, const pkginfo_t
& info
)
153 packages
[name
] = info
;
156 bool pkgutil::db_find_pkg(const string
& name
)
158 return (packages
.find(name
) != packages
.end());
161 void pkgutil::db_rm_pkg(const string
& name
)
163 set
<string
> files
= packages
[name
].files
;
164 packages
.erase(name
);
167 cerr
<< "Removing package phase 1 (all files in package):" << endl
;
168 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
172 // Don't delete files that still have references
173 for (packages_t::const_iterator i
= packages
.begin(); i
!= packages
.end(); ++i
)
174 for (set
<string
>::const_iterator j
= i
->second
.files
.begin(); j
!= i
->second
.files
.end(); ++j
)
178 cerr
<< "Removing package phase 2 (files that still have references excluded):" << endl
;
179 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
184 for (set
<string
>::const_reverse_iterator i
= files
.rbegin(); i
!= files
.rend(); ++i
) {
185 const string filename
= root
+ *i
;
186 if (file_exists(filename
) && remove(filename
.c_str()) == -1) {
187 const char* msg
= strerror(errno
);
188 cerr
<< utilname
<< ": could not remove " << filename
<< ": " << msg
<< endl
;
193 void pkgutil::db_rm_pkg(const string
& name
, const set
<string
>& keep_list
)
195 set
<string
> files
= packages
[name
].files
;
196 packages
.erase(name
);
199 cerr
<< "Removing package phase 1 (all files in package):" << endl
;
200 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
204 // Don't delete files found in the keep list
205 for (set
<string
>::const_iterator i
= keep_list
.begin(); i
!= keep_list
.end(); ++i
)
209 cerr
<< "Removing package phase 2 (files that is in the keep list excluded):" << endl
;
210 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
214 // Don't delete files that still have references
215 for (packages_t::const_iterator i
= packages
.begin(); i
!= packages
.end(); ++i
)
216 for (set
<string
>::const_iterator j
= i
->second
.files
.begin(); j
!= i
->second
.files
.end(); ++j
)
220 cerr
<< "Removing package phase 3 (files that still have references excluded):" << endl
;
221 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
226 for (set
<string
>::const_reverse_iterator i
= files
.rbegin(); i
!= files
.rend(); ++i
) {
227 const string filename
= root
+ *i
;
228 if (file_exists(filename
) && remove(filename
.c_str()) == -1) {
229 if (errno
== ENOTEMPTY
)
231 const char* msg
= strerror(errno
);
232 cerr
<< utilname
<< ": could not remove " << filename
<< ": " << msg
<< endl
;
237 void pkgutil::db_rm_files(set
<string
> files
, const set
<string
>& keep_list
)
239 // Remove all references
240 for (packages_t::iterator i
= packages
.begin(); i
!= packages
.end(); ++i
)
241 for (set
<string
>::const_iterator j
= files
.begin(); j
!= files
.end(); ++j
)
242 i
->second
.files
.erase(*j
);
245 cerr
<< "Removing files:" << endl
;
246 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
250 // Don't delete files found in the keep list
251 for (set
<string
>::const_iterator i
= keep_list
.begin(); i
!= keep_list
.end(); ++i
)
255 for (set
<string
>::const_reverse_iterator i
= files
.rbegin(); i
!= files
.rend(); ++i
) {
256 const string filename
= root
+ *i
;
257 if (file_exists(filename
) && remove(filename
.c_str()) == -1) {
258 if (errno
== ENOTEMPTY
)
260 const char* msg
= strerror(errno
);
261 cerr
<< utilname
<< ": could not remove " << filename
<< ": " << msg
<< endl
;
266 set
<string
> pkgutil::db_find_conflicts(const string
& name
, const pkginfo_t
& info
)
270 // Find conflicting files in database
271 for (packages_t::const_iterator i
= packages
.begin(); i
!= packages
.end(); ++i
) {
272 if (i
->first
!= name
) {
273 set_intersection(info
.files
.begin(), info
.files
.end(),
274 i
->second
.files
.begin(), i
->second
.files
.end(),
275 inserter(files
, files
.end()));
280 cerr
<< "Conflicts phase 1 (conflicts in database):" << endl
;
281 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
285 // Find conflicting files in filesystem
286 for (set
<string
>::iterator i
= info
.files
.begin(); i
!= info
.files
.end(); ++i
) {
287 const string filename
= root
+ *i
;
288 if (file_exists(filename
) && files
.find(*i
) == files
.end())
289 files
.insert(files
.end(), *i
);
293 cerr
<< "Conflicts phase 2 (conflicts in filesystem added):" << endl
;
294 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
298 // Exclude directories
299 set
<string
> tmp
= files
;
300 for (set
<string
>::const_iterator i
= tmp
.begin(); i
!= tmp
.end(); ++i
) {
301 if ((*i
)[i
->length() - 1] == '/')
306 cerr
<< "Conflicts phase 3 (directories excluded):" << endl
;
307 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
311 // If this is an upgrade, remove files already owned by this package
312 if (packages
.find(name
) != packages
.end()) {
313 for (set
<string
>::const_iterator i
= packages
[name
].files
.begin(); i
!= packages
[name
].files
.end(); ++i
)
317 cerr
<< "Conflicts phase 4 (files already owned by this package excluded):" << endl
;
318 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
326 pair
<string
, pkgutil::pkginfo_t
> pkgutil::pkg_open(const string
& filename
) const
328 pair
<string
, pkginfo_t
> result
;
330 struct archive
* archive
;
331 struct archive_entry
* entry
;
333 // Extract name and version from filename
334 string
basename(filename
, filename
.rfind('/') + 1);
335 string
name(basename
, 0, basename
.find(VERSION_DELIM
));
336 string
version(basename
, 0, basename
.rfind(PKG_EXT
));
337 version
.erase(0, version
.find(VERSION_DELIM
) == string::npos
? string::npos
: version
.find(VERSION_DELIM
) + 1);
339 if (name
.empty() || version
.empty())
340 throw runtime_error("could not determine name and/or version of " + basename
+ ": Invalid package name");
343 result
.second
.version
= version
;
345 archive
= archive_read_new();
346 archive_read_support_compression_all(archive
);
347 archive_read_support_format_all(archive
);
349 if (archive_read_open_filename(archive
,
350 const_cast<char*>(filename
.c_str()),
351 ARCHIVE_DEFAULT_BYTES_PER_BLOCK
) != ARCHIVE_OK
)
352 throw runtime_error_with_errno("could not open " + filename
, archive_errno(archive
));
354 for (i
= 0; archive_read_next_header(archive
, &entry
) ==
357 result
.second
.files
.insert(result
.second
.files
.end(),
358 archive_entry_pathname(entry
));
360 mode_t mode
= archive_entry_mode(entry
);
363 archive_read_data_skip(archive
) != ARCHIVE_OK
)
364 throw runtime_error_with_errno("could not read " + filename
, archive_errno(archive
));
368 if (archive_errno(archive
) == 0)
369 throw runtime_error("empty package");
371 throw runtime_error("could not read " + filename
);
374 archive_read_finish(archive
);
379 void pkgutil::pkg_install(const string
& filename
, const set
<string
>& keep_list
, const set
<string
>& non_install_list
) const
381 struct archive
* archive
;
382 struct archive_entry
* entry
;
385 archive
= archive_read_new();
386 archive_read_support_compression_all(archive
);
387 archive_read_support_format_all(archive
);
389 if (archive_read_open_filename(archive
,
390 const_cast<char*>(filename
.c_str()),
391 ARCHIVE_DEFAULT_BYTES_PER_BLOCK
) != ARCHIVE_OK
)
392 throw runtime_error_with_errno("could not open " + filename
, archive_errno(archive
));
396 for (i
= 0; archive_read_next_header(archive
, &entry
) ==
398 string archive_filename
= archive_entry_pathname(entry
);
399 string reject_dir
= trim_filename(root
+ string("/") + string(PKG_REJECTED
));
400 string original_filename
= trim_filename(root
+ string("/") + archive_filename
);
401 string real_filename
= original_filename
;
403 // Check if file is filtered out via INSTALL
404 if (non_install_list
.find(archive_filename
) != non_install_list
.end()) {
407 cout
<< utilname
<< ": ignoring " << archive_filename
<< endl
;
409 mode
= archive_entry_mode(entry
);
412 archive_read_data_skip(archive
);
417 // Check if file should be rejected
418 if (file_exists(real_filename
) && keep_list
.find(archive_filename
) != keep_list
.end())
419 real_filename
= trim_filename(reject_dir
+ string("/") + archive_filename
);
421 archive_entry_set_pathname(entry
, const_cast<char*>
422 (real_filename
.c_str()));
425 unsigned int flags
= ARCHIVE_EXTRACT_OWNER
| ARCHIVE_EXTRACT_PERM
| ARCHIVE_EXTRACT_UNLINK
;
427 if (archive_read_extract(archive
, entry
, flags
) != ARCHIVE_OK
) {
428 // If a file fails to install we just print an error message and
429 // continue trying to install the rest of the package.
430 const char* msg
= archive_error_string(archive
);
431 cerr
<< utilname
<< ": could not install " + archive_filename
<< ": " << msg
<< endl
;
435 // Check rejected file
436 if (real_filename
!= original_filename
) {
437 bool remove_file
= false;
438 mode_t mode
= archive_entry_mode(entry
);
442 remove_file
= permissions_equal(real_filename
, original_filename
);
445 remove_file
= permissions_equal(real_filename
, original_filename
) &&
446 (file_empty(real_filename
) || file_equal(real_filename
, original_filename
));
448 // Remove rejected file or signal about its existence
450 file_remove(reject_dir
, real_filename
);
452 cout
<< utilname
<< ": rejecting " << archive_filename
<< ", keeping existing version" << endl
;
457 if (archive_errno(archive
) == 0)
458 throw runtime_error("empty package");
460 throw runtime_error("could not read " + filename
);
463 archive_read_finish(archive
);
466 void pkgutil::ldconfig() const
468 // Only execute ldconfig if /etc/ld.so.conf exists
469 if (file_exists(root
+ LDCONFIG_CONF
)) {
473 throw runtime_error_with_errno("fork() failed");
476 execl(LDCONFIG
, LDCONFIG
, "-r", root
.c_str(), (char *) 0);
477 const char* msg
= strerror(errno
);
478 cerr
<< utilname
<< ": could not execute " << LDCONFIG
<< ": " << msg
<< endl
;
481 if (waitpid(pid
, 0, 0) == -1)
482 throw runtime_error_with_errno("waitpid() failed");
487 void pkgutil::pkg_footprint(string
& filename
) const
490 struct archive
* archive
;
491 struct archive_entry
* entry
;
493 map
<string
, mode_t
> hardlink_target_modes
;
495 // We first do a run over the archive and remember the modes
497 // In the second run, we print the footprint - using the stored
498 // modes for hardlinks.
500 // FIXME the code duplication here is butt ugly
501 archive
= archive_read_new();
502 archive_read_support_compression_all(archive
);
503 archive_read_support_format_all(archive
);
505 if (archive_read_open_filename(archive
,
506 const_cast<char*>(filename
.c_str()),
507 ARCHIVE_DEFAULT_BYTES_PER_BLOCK
) != ARCHIVE_OK
)
508 throw runtime_error_with_errno("could not open " + filename
, archive_errno(archive
));
510 for (i
= 0; archive_read_next_header(archive
, &entry
) ==
513 mode_t mode
= archive_entry_mode(entry
);
515 if (!archive_entry_hardlink(entry
)) {
516 const char *s
= archive_entry_pathname(entry
);
518 hardlink_target_modes
[s
] = mode
;
521 if (S_ISREG(mode
) && archive_read_data_skip(archive
))
522 throw runtime_error_with_errno("could not read " + filename
, archive_errno(archive
));
525 archive_read_finish(archive
);
527 // Too bad, there doesn't seem to be a way to reuse our archive
529 archive
= archive_read_new();
530 archive_read_support_compression_all(archive
);
531 archive_read_support_format_all(archive
);
533 if (archive_read_open_filename(archive
,
534 const_cast<char*>(filename
.c_str()),
535 ARCHIVE_DEFAULT_BYTES_PER_BLOCK
) != ARCHIVE_OK
)
536 throw runtime_error_with_errno("could not open " + filename
, archive_errno(archive
));
538 for (i
= 0; archive_read_next_header(archive
, &entry
) ==
540 mode_t mode
= archive_entry_mode(entry
);
542 // Access permissions
544 // Access permissions on symlinks differ among filesystems, e.g. XFS and ext2 have different.
545 // To avoid getting different footprints we always use "lrwxrwxrwx".
546 cout
<< "lrwxrwxrwx";
548 const char *h
= archive_entry_hardlink(entry
);
551 cout
<< mtos(hardlink_target_modes
[h
]);
559 uid_t uid
= archive_entry_uid(entry
);
560 struct passwd
* pw
= getpwuid(uid
);
569 gid_t gid
= archive_entry_gid(entry
);
570 struct group
* gr
= getgrgid(gid
);
577 cout
<< '\t' << archive_entry_pathname(entry
);
582 cout
<< " -> " << archive_entry_symlink(entry
);
583 } else if (S_ISCHR(mode
) ||
586 cout
<< " (" << archive_entry_rdevmajor(entry
)
587 << ", " << archive_entry_rdevminor(entry
)
589 } else if (S_ISREG(mode
) &&
590 archive_entry_size(entry
) == 0) {
591 // Empty regular file
597 if (S_ISREG(mode
) && archive_read_data_skip(archive
))
598 throw runtime_error_with_errno("could not read " + filename
, archive_errno(archive
));
602 if (archive_errno(archive
) == 0)
603 throw runtime_error("empty package");
605 throw runtime_error("could not read " + filename
);
608 archive_read_finish(archive
);
611 void pkgutil::print_version() const
613 cout
<< utilname
<< " (pkgutils) " << VERSION
<< endl
;
616 db_lock::db_lock(const string
& root
, bool exclusive
)
619 const string dirname
= trim_filename(root
+ string("/") + PKG_DIR
);
621 if (!(dir
= opendir(dirname
.c_str())))
622 throw runtime_error_with_errno("could not read directory " + dirname
);
624 if (flock(dirfd(dir
), (exclusive
? LOCK_EX
: LOCK_SH
) | LOCK_NB
) == -1) {
625 if (errno
== EWOULDBLOCK
)
626 throw runtime_error("package database is currently locked by another process");
628 throw runtime_error_with_errno("could not lock directory " + dirname
);
635 flock(dirfd(dir
), LOCK_UN
);
640 void assert_argument(char** argv
, int argc
, int index
)
642 if (argc
- 1 < index
+ 1)
643 throw runtime_error("option " + string(argv
[index
]) + " requires an argument");
646 string
itos(unsigned int value
)
649 sprintf(buf
, "%u", value
);
653 string
mtos(mode_t mode
)
658 switch (mode
& S_IFMT
) {
659 case S_IFREG
: s
+= '-'; break; // Regular
660 case S_IFDIR
: s
+= 'd'; break; // Directory
661 case S_IFLNK
: s
+= 'l'; break; // Symbolic link
662 case S_IFCHR
: s
+= 'c'; break; // Character special
663 case S_IFBLK
: s
+= 'b'; break; // Block special
664 case S_IFSOCK
: s
+= 's'; break; // Socket
665 case S_IFIFO
: s
+= 'p'; break; // Fifo
666 default: s
+= '?'; break; // Unknown
670 s
+= (mode
& S_IRUSR
) ? 'r' : '-';
671 s
+= (mode
& S_IWUSR
) ? 'w' : '-';
672 switch (mode
& (S_IXUSR
| S_ISUID
)) {
673 case S_IXUSR
: s
+= 'x'; break;
674 case S_ISUID
: s
+= 'S'; break;
675 case S_IXUSR
| S_ISUID
: s
+= 's'; break;
676 default: s
+= '-'; break;
680 s
+= (mode
& S_IRGRP
) ? 'r' : '-';
681 s
+= (mode
& S_IWGRP
) ? 'w' : '-';
682 switch (mode
& (S_IXGRP
| S_ISGID
)) {
683 case S_IXGRP
: s
+= 'x'; break;
684 case S_ISGID
: s
+= 'S'; break;
685 case S_IXGRP
| S_ISGID
: s
+= 's'; break;
686 default: s
+= '-'; break;
690 s
+= (mode
& S_IROTH
) ? 'r' : '-';
691 s
+= (mode
& S_IWOTH
) ? 'w' : '-';
692 switch (mode
& (S_IXOTH
| S_ISVTX
)) {
693 case S_IXOTH
: s
+= 'x'; break;
694 case S_ISVTX
: s
+= 'T'; break;
695 case S_IXOTH
| S_ISVTX
: s
+= 't'; break;
696 default: s
+= '-'; break;
702 int unistd_gzopen(char* pathname
, int flags
, mode_t mode
)
706 switch (flags
& O_ACCMODE
) {
724 if ((fd
= open(pathname
, flags
, mode
)) == -1)
727 if ((flags
& O_CREAT
) && fchmod(fd
, mode
))
730 if (!(gz_file
= gzdopen(fd
, gz_mode
))) {
738 string
trim_filename(const string
& filename
)
741 string result
= filename
;
743 for (string::size_type pos
= result
.find(search
); pos
!= string::npos
; pos
= result
.find(search
))
744 result
.replace(pos
, search
.size(), "/");
749 bool file_exists(const string
& filename
)
752 return !lstat(filename
.c_str(), &buf
);
755 bool file_empty(const string
& filename
)
759 if (lstat(filename
.c_str(), &buf
) == -1)
762 return (S_ISREG(buf
.st_mode
) && buf
.st_size
== 0);
765 bool file_equal(const string
& file1
, const string
& file2
)
767 struct stat buf1
, buf2
;
769 if (lstat(file1
.c_str(), &buf1
) == -1)
772 if (lstat(file2
.c_str(), &buf2
) == -1)
776 if (S_ISREG(buf1
.st_mode
) && S_ISREG(buf2
.st_mode
)) {
777 ifstream
f1(file1
.c_str());
778 ifstream
f2(file2
.c_str());
786 f1
.read(buffer1
, 4096);
787 f2
.read(buffer2
, 4096);
788 if (f1
.gcount() != f2
.gcount() ||
789 memcmp(buffer1
, buffer2
, f1
.gcount()) ||
790 f1
.eof() != f2
.eof())
797 else if (S_ISLNK(buf1
.st_mode
) && S_ISLNK(buf2
.st_mode
)) {
798 char symlink1
[MAXPATHLEN
];
799 char symlink2
[MAXPATHLEN
];
801 memset(symlink1
, 0, MAXPATHLEN
);
802 memset(symlink2
, 0, MAXPATHLEN
);
804 if (readlink(file1
.c_str(), symlink1
, MAXPATHLEN
- 1) == -1)
807 if (readlink(file2
.c_str(), symlink2
, MAXPATHLEN
- 1) == -1)
810 return !strncmp(symlink1
, symlink2
, MAXPATHLEN
);
813 else if (S_ISCHR(buf1
.st_mode
) && S_ISCHR(buf2
.st_mode
)) {
814 return buf1
.st_dev
== buf2
.st_dev
;
817 else if (S_ISBLK(buf1
.st_mode
) && S_ISBLK(buf2
.st_mode
)) {
818 return buf1
.st_dev
== buf2
.st_dev
;
824 bool permissions_equal(const string
& file1
, const string
& file2
)
829 if (lstat(file1
.c_str(), &buf1
) == -1)
832 if (lstat(file2
.c_str(), &buf2
) == -1)
835 return(buf1
.st_mode
== buf2
.st_mode
) &&
836 (buf1
.st_uid
== buf2
.st_uid
) &&
837 (buf1
.st_gid
== buf2
.st_gid
);
840 void file_remove(const string
& basedir
, const string
& filename
)
842 if (filename
!= basedir
&& !remove(filename
.c_str())) {
843 char* path
= strdup(filename
.c_str());
844 file_remove(basedir
, dirname(path
));