Compare commits

...

219 Commits

Author SHA1 Message Date
Ilia Ross
8c5470e3fc Add API to exclude module calls from logging 2026-03-10 23:25:22 +02:00
Ilia Ross
f1d580de1b Add UI for testing two-factor after enrollment
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
aafecf0fb2
2026-03-10 22:33:02 +02:00
Ilia Ross
f0b9152ae5 Fix query monitor MariaDB driver handling 2026-03-10 18:05:59 +02:00
Ilia Ross
5862cde30f Fix cluster software remote eval escaping and target validation (#11) 2026-03-10 17:29:18 +02:00
Ilia Ross
2de16faafc Fix Solaris format module option and device command injection (#10) 2026-03-10 17:29:17 +02:00
Ilia Ross
098544473f Fix PostgreSQL user/group SQL quoting with shared helpers (#9) 2026-03-10 17:29:17 +02:00
Ilia Ross
2c82255179 Fix MySQL save handlers with parameterized SQL deletes (#8) 2026-03-10 17:29:17 +02:00
Ilia Ross
fe5cf97cef Fix query monitor driver eval with strict whitelist (#7) 2026-03-10 17:29:17 +02:00
Ilia Ross
01d650ca06 Fix fsdump stored extra option validation on save (#6) 2026-03-10 17:29:17 +02:00
Ilia Ross
e19dca6882 Fix fsdump restore command argument injection handling (#5) 2026-03-10 17:29:16 +02:00
Ilia Ross
3934ae3e73 Fix qmail alias file write path and tempfile safety (#4) 2026-03-10 17:29:16 +02:00
Ilia Ross
1a0aa44dba Fix IPsec up command injection via conn name (#3) 2026-03-10 17:29:16 +02:00
Ilia Ross
2c211e557e Fix runlevel change input validation and escaping (#2) 2026-03-10 17:29:16 +02:00
Ilia Ross
b3ec013fc9 Fix init start/stop command injection guard (#1) 2026-03-10 17:29:15 +02:00
Ilia Ross
b8481cc1e5 Add check for non public IP API 2026-03-10 13:49:03 +02:00
Ilia Ross
c0900ffaf8 Add quote literal escape API 2026-03-10 13:38:13 +02:00
Jamie Cameron
302f635651 Only fetch user list once per domain
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2634
2026-03-07 16:53:56 -08:00
Jamie Cameron
d5feb72572 Cleanup use of local
https://github.com/webmin/webmin/issues/2634
2026-03-07 13:32:00 -08:00
Jamie Cameron
cd7f867c09 Only load virtual-server module once
https://github.com/webmin/webmin/issues/2634
2026-03-07 13:29:37 -08:00
Jamie Cameron
16245144eb Cache block_size for each filesystem
https://github.com/webmin/webmin/issues/2634
2026-03-07 12:48:29 -08:00
Jamie Cameron
083b4c7826 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-05 20:46:29 -08:00
Jamie Cameron
019cc10b79 Use formal function parameters 2026-03-05 20:41:45 -08:00
Ilia Ross
5d860a6728 Fix to avoid showing a message if the user explicitly prefers tempdir
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-01 15:44:09 +02:00
Ilia Ross
2e4ec03670 Fix language files 2026-03-01 15:27:48 +02:00
Ilia Ross
b4984e495d Fix to ensure /tmp on tmpfs is displayed as separate mount 2026-03-01 15:15:17 +02:00
Ilia Ross
d7434c61a2 Fix to show post-save message about 2FA
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/no-qr-code-displayed-when-selectinc-totp/136703/5
2026-02-28 22:17:11 +02:00
Ilia Ross
f204480957 Fix formatting 2026-02-28 18:49:31 +02:00
Ilia Ross
04f1426f33 Add ability to get module edition 2026-02-28 18:23:13 +02:00
Ilia Ross
f10385417c Fix typo
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-27 12:09:25 +02:00
Jamie Cameron
a502eff346 Also warn about RAM disks
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-25 15:16:43 -08:00
Jamie Cameron
a3416dc830 Also warn about RAM disks 2026-02-25 15:15:20 -08:00
Ilia Ross
3354a0cc2f Fix missing class 2026-02-26 01:09:57 +02:00
Ilia Ross
2a7806be31 Fix to use proper params in parse_http_url sub
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
*Note: Additionally support ability not to normalize default ports
2026-02-25 12:34:24 +02:00
Ilia Ross
d4850c3aa3 Fix as the small limit is much smaller
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
(does it even make sense to have?)

https://forum.virtualmin.com/t/backup-and-restore-failures-no-space-left-on-device/136639/32?u=ilia
2026-02-25 01:40:02 +02:00
Ilia Ross
7cbe00ade2 Fix the message as it's already in the alert box
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-24 11:57:56 +02:00
Ilia Ross
540181ae22 Fix minimum size check for Webmin temp directory 2026-02-24 11:56:10 +02:00
Jamie Cameron
38cae2fae2 Add a warning if the temp files directory is less than 10 MB
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-23 21:13:03 -08:00
Jamie Cameron
9a14c437b8 Merge branch 'master' of github.com:webmin/webmin 2026-02-23 20:58:59 -08:00
Jamie Cameron
bd3fd5d49b Fix perl formatting 2026-02-23 20:58:55 -08:00
Ilia Ross
fb9f7ead7c Fix read_file_lines_as_user to use proper file name and params
https://forum.virtualmin.com/t/error-installing-wordpress-from-web-apps-after-update/136447/19?u=ilia
2026-02-24 01:24:21 +02:00
Jamie Cameron
92ac52893e Revert back to using /tmp/.webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-21 15:34:24 -08:00
Ilia Ross
2397653d55 Fix IPv6 hostname matching for alwaysresolve access rules
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
* Note: Fix Miniserv IPv6 hostname resolution and matching used by access control when `alwaysresolve` is enabled:
  1. Correct `to_ip6address()` success handling (before getaddrinfo result was interpreted backwards)
  2. In `ip_match()`, resolve hostnames with `to_ip6address()` for IPv6 clients instead of IPv4-only `to_ipaddress()`
  3. Canonicalize IPv6 addresses before reverse and forward verification to avoid format-based mismatches.
  4. Mirror the IPv6 logic change in "webmin/webmin-lib.pl"

https://forum.virtualmin.com/t/webmin-access-control-for-domain-names-with-ipv6/136661?u=ilia
2026-02-21 13:30:08 +02:00
Ilia Ross
209a2cbbc3 Fix to use /var/tmpas default temp dir instead of /var/cache (not rw by user)
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-20 16:09:30 +02:00
Ilia Ross
29c2c6f59d Fix to prefer /var/cache or /var/tmp over /tmp for default temp directory 2026-02-20 15:35:47 +02:00
Ilia Ross
c89dc4996f Fix to de-hardcode default temp directory path
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-20 15:16:01 +02:00
Ilia Ross
bfc1f10b38 Fix to avoid leaking to neighboring property when size is unset
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-19 23:22:48 +02:00
Ilia Ross
56a1c323c8 Fix def min width 2026-02-19 23:16:41 +02:00
Jamie Cameron
f7384bbf05 Properly propogate error messages
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-19 11:33:20 -08:00
Ilia Ross
9cd60f4741 Fix to disable directory listing by default
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-18 16:24:58 +02:00
Ilia Ross
f778af84a0 Fix create_launchd_agent to support optional load parameter
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/authentic-theme/issues/1729#issuecomment-3899950457
2026-02-16 20:15:52 +02:00
Ilia Ross
2eb2be2318 Add support for updating launchd agents with start init wrapper
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/authentic-theme/issues/1729
2026-02-13 17:44:07 +02:00
Jamie Cameron
38352f5c01 Deprecate the unused template params support in hlink.cgi
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 16:16:33 -08:00
Ilia Ross
07a11f7de6 Fix to use quotemeta to prevent shell injection in Useradmin module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 13:03:27 +02:00
Ilia Ross
8b76f2ffc8 Fix OS list field separations to do correctly 2026-02-11 11:49:02 +02:00
Jamie Cameron
6d014e31cb Merge pull request #2628 from pbobbenb/macOS-detection
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Added missing macOS versions.
2026-02-10 18:12:11 -08:00
Pär Boberg
3b05ce756d Added missing macOS versions. 2026-02-11 00:06:07 +01:00
Ilia Ross
7f322a5df6 Fix to use quotemeta to prevent shell injection in Logviewer module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 01:05:36 +02:00
Ilia Ross
c47047953b Fix to use quotemeta to prevent shell injection in Software module 2026-02-11 00:53:29 +02:00
Ilia Ross
f30560d61b Fix to use quotemeta to prevent shell injection in Cron module 2026-02-10 22:09:07 +02:00
Ilia Ross
e13123bed1 Fix to use quotemeta to prevent shell injection in Proc module 2026-02-10 21:40:15 +02:00
Ilia Ross
821548354d Fix file opening syntax 2026-02-10 20:34:51 +02:00
Jamie Cameron
3a1ea4682d Fix merge conflict 2026-02-10 10:24:58 -08:00
Jamie Cameron
3713ee01b8 The server_root function doesn't need the global config as a parameter 2026-02-10 10:21:22 -08:00
Ilia Ross
de87e037d4 Fix to use quotemeta to prevent shell injection in FSdump module 2026-02-10 19:58:24 +02:00
Ilia Ross
e805f95b48 Fix regression
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-10 18:59:02 +02:00
Ilia Ross
b782a124b5 Fix to use quotemeta to prevent shell injection in Quota module 2026-02-10 18:56:37 +02:00
Ilia Ross
c85d04cc74 Fix to use proper validation before writing files 2026-02-10 16:23:58 +02:00
Ilia Ross
a6bd5c6ebc Fix to use quotemeta to prevent shell injection in Apache module 2026-02-10 15:57:07 +02:00
Ilia Ross
9fd37b7404 Fix to use quotemeta to prevent shell injection in BIND module 2026-02-10 15:40:29 +02:00
Ilia Ross
ac9456b368 Fix to use quotemeta to prevent shell injection in fetchmail module
Ref.: 50a2460d-441a-4bc6 (VULN-003)
2026-02-10 14:45:20 +02:00
Ilia Ross
399d7a8651 Fix to use quotemeta to prevent shell injection in fdisk module
Ref.: 796677885d (VULN-001)
2026-02-10 14:05:16 +02:00
Ilia Ross
c87712ef4e Fix to use quotemeta to prevent shell injection in usermin module
Ref.: a8417099-d3bc-468a (VULN-004)
2026-02-10 13:21:56 +02:00
Ilia Ross
c47b63bfcf Fix to use quotemeta to prevent shell injection in mount module
Ref.: 97bf3c03-fbe3-4ee0
2026-02-10 12:57:59 +02:00
Jamie Cameron
3a86d961ce Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-09 10:57:04 -08:00
Jamie Cameron
402b21abac Fix permissions 2026-02-09 10:56:52 -08:00
Jamie Cameron
0816c0a71e Revert "Add extra protection against packets somehow arriving before handshake is called"
This reverts commit d9c651d06d.
2026-02-09 10:56:23 -08:00
Ilia Ross
a38114e623 Fix to use flags unconditionally as supported by all modern distros
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-08 16:20:29 +02:00
Ilia Ross
a655f875cf Fix log filtering logic to work with new regex flag correctly 2026-02-08 16:16:57 +02:00
Ilia Ross
79b5b307ed Add regex filter option to log viewer 2026-02-08 15:41:01 +02:00
Ilia Ross
5a3b0cfd2d Add context lines option for log viewer filter
https://github.com/virtualmin/virtualmin-gpl/issues/1174
2026-02-08 14:31:16 +02:00
Ilia Ross
fa32009fbb Merge pull request #2625 from karmantyu/master
Improved disk detection, smart status, partition mounting, added labe…
2026-02-08 11:11:26 +02:00
karmantyu
a9f4fdc8ca Corrected escape, removed broad, unanchored regex checks . 2026-02-08 08:34:32 +01:00
karmantyu
b593501cff Some fixes.
All $err type error messages in HTML are safely escaped now.
URL-encoding in links:
I have implemented urlize() in all places where user input ($in{'device'}, $in{'slice'}, $in{'part'}) was included in the URL (footer/redirect/other link), e.g. edit_slice.cgi?device=...&slice=....
Affected files include: create_part.cgi, create_slice.cgi, delete_part.cgi, delete_slice.cgi, change_slice_label.cgi, part_form.cgi, slice_form.cgi, edit_slice.cgi, edit_part.cgi, fsck.cgi, newfs.cgi, newfs_form.cgi, save_part.cgi, save_slice.cgi, save_slice_label.cgi, zfs_create.cgi, zvol_create.cgi.
2026-02-07 19:43:08 +01:00
karmantyu
107a2e42f4 Merge branch 'webmin:master' into master 2026-02-07 19:04:42 +01:00
Jamie Cameron
3572049284 Handle error listing zones more cleanly
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/how-to-configure-secondary-dns/136557
2026-02-06 21:08:06 -08:00
karmantyu
d9be1b956f bsdfdisk-lib.pl patched to enumerate nda* devices
Related to the NVMe issue: I patched bsdfdisk-lib.pl to enumerate nda* devices (in addition to nvd*), since the reporter uses the nda driver.
2026-02-06 18:38:10 +01:00
karmantyu
b2f54c36ca Some tidy up and numeric validation + quote_path. 2026-02-04 08:42:44 +01:00
karmantyu
cbc96170c4 Delete save_slice.cgi 2026-02-04 08:42:16 +01:00
karmantyu
9d01f3cd9e Delete bsdfdisk-lib.pl 2026-02-04 08:41:34 +01:00
karmantyu
ca7c57b181 Some tidy up and numeric validation + quote_path. 2026-02-04 08:39:11 +01:00
karmantyu
859580a224 Tidy up. 2026-02-02 07:50:07 +01:00
karmantyu
30b2e8b6d4 Security and other minor changes. 2026-02-01 11:39:57 +01:00
karmantyu
63fd68b2f9 proper escape the dataset name 2026-01-31 21:16:53 +01:00
karmantyu
8402db53d5 Improved disk detection, smart status, partition mounting, added label handling, zfs filesystem/volume creation. 2026-01-31 20:55:00 +01:00
Jamie Cameron
736c320a60 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-29 20:02:47 -08:00
Jamie Cameron
0ecbb04e00 Treat fuseblk as a local disk filesystem
https://github.com/webmin/webmin/issues/2624
2026-01-29 20:00:34 -08:00
Ilia Ross
2dfa6cfc1b Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-27 02:24:59 +02:00
Jamie Cameron
4f438906d2 New version bump 2026-01-26 14:25:51 -08:00
Jamie Cameron
c8fcde9579 remove useless brackets 2026-01-26 14:25:06 -08:00
Ilia Ross
e6184a0036 Fix to only check for uniqueness 2026-01-26 22:55:18 +02:00
Jamie Cameron
54bb3ee293 No need to check for undef values, as this can never happen 2026-01-26 12:54:55 -08:00
Ilia Ross
bfb496ca29 Fix temporary file upload directory
e8e804ddca (r175609008)
2026-01-26 21:59:39 +02:00
Jamie Cameron
58c9efd7ea Merge pull request #2623 from karmantyu/master
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Add files via upload
2026-01-26 10:52:44 -08:00
Jamie Cameron
534c529705 Revert "Fix to use universal upload tracking directory"
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
This reverts commit e8e804ddca.
2026-01-25 21:17:14 -08:00
Ilia Ross
2d0063129c Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-25 13:29:02 +02:00
Ilia Ross
ad37eabdfe Update Xterm.js to the latest version
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-25 01:04:44 +02:00
Ilia Ross
6d3da61b95 Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-24 00:49:57 +02:00
Ilia Ross
4d05e8a2d0 Merge branch 'master' of github.com:webmin/webmin 2026-01-24 00:38:18 +02:00
Ilia Ross
e8e804ddca Fix to use universal upload tracking directory
https://forum.virtualmin.com/t/upload-progress-bar-not-showing/136374?u=ilia
2026-01-24 00:37:52 +02:00
Ilia Ross
202a1b0b78 Add API to pick a writable system-wide temp directory 2026-01-24 00:34:06 +02:00
Jamie Cameron
3c1c327530 Show the full version properly 2026-01-23 13:46:21 -08:00
Jamie Cameron
2d7900d550 Fix comment 2026-01-23 13:42:41 -08:00
karmantyu
032f4447db Add files via upload
gpart edition
2026-01-23 19:41:57 +01:00
Ilia Ross
e9586fb2d8 Fix to use fixed PHP 5.6 label in the dropdown
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2622#issuecomment-3787051348
2026-01-23 14:04:39 +02:00
Jamie Cameron
9d95b5c977 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-22 15:33:27 -08:00
Jamie Cameron
d9c651d06d Add extra protection against packets somehow arriving before handshake is called 2026-01-22 15:33:02 -08:00
Ilia Ross
c70bae48aa Fix release date in changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[rebuild-all-modules]
2026-01-22 12:16:26 +02:00
Jamie Cameron
83abdc8858 Logrotate requires 0644 or 0444 permissions
https://github.com/webmin/webmin/issues/2396
2026-01-13 15:24:54 -08:00
Ilia Ross
2833450b48 Update changelog (ongoing) 2026-01-10 20:47:43 +02:00
Ilia Ross
a0c023637f Fix to improve the message when socket auth is used 2026-01-10 20:44:59 +02:00
Ilia Ross
6721e13a6b Fix MariaDB version 10.3 already supports plugins 2026-01-10 20:31:47 +02:00
Ilia Ross
2b2814fdf4 Update changelog for 2.621 2026-01-09 18:51:04 +02:00
Ilia Ross
4dacdc31f6 Fix to prevents NAT from dropping idle connections
https://forum.virtualmin.com/t/problem-with-backup-of-large-virtual-servers-to-a-remote-webmin-server/136186/46
2026-01-09 18:14:45 +02:00
Ilia Ross
2627ba289e Add better logging for RPC 2026-01-09 18:12:37 +02:00
Ilia Ross
b4a67a0d90 Fix dates consistency in the changelog 2026-01-09 14:28:12 +02:00
Ilia Ross
63af275296 Update changelog for 2.620 2026-01-09 14:27:46 +02:00
Ilia Ross
5f1bbc4ac2 Fix to mention if user is missing in error message 2026-01-09 01:06:47 +02:00
Jamie Cameron
124147205f We don't use const anywhere else in Webmin, so don't use it here 2026-01-08 13:02:18 -08:00
Ilia Ross
e9b3ed9624 Fix incorrectly resolved conflict 2026-01-08 22:12:42 +02:00
Jamie Cameron
ccde77c0e2 Merge branch 'master' of github.com:webmin/webmin
Conflicts:
	fastrpc.cgi
2026-01-08 10:50:22 -08:00
Jamie Cameron
ffd8171e18 Fork can fail if there are no more processes left 2026-01-08 10:45:39 -08:00
Ilia Ross
65ab502176 Add an option to configure RPC timeout 2026-01-08 18:30:41 +02:00
Ilia Ross
013aa5a5c6 Fix to make timeout cleanly configurable 2026-01-08 18:28:43 +02:00
Ilia Ross
12dca80535 Fix to improve fork handling and zombie reaping
*Note: Even though the current code generally works, it's better to properly reap child processes with waitpid to avoid infinite timeouts and also clean up inherited FDs.
2026-01-08 16:40:44 +02:00
Ilia Ross
89ee635de3 Fix key order 2026-01-08 14:37:57 +02:00
Jamie Cameron
af3ddd652f Merge branch 'master' of github.com:webmin/webmin 2026-01-07 21:39:48 -08:00
Jamie Cameron
e6d214b776 Keep track of sub-processes doing reads or writes, and don't timeout connections while they are in progress
https://forum.virtualmin.com/t/problem-with-backup-of-large-virtual-servers-to-a-remote-webmin-server/136186
2026-01-07 21:39:27 -08:00
Ilia Ross
45521e9c30 Fix inessential semicolon 2026-01-07 23:13:08 +02:00
Jamie Cameron
123beb03d8 Add a config option to increase the RPC timeout 2026-01-07 11:08:40 -08:00
Jamie Cameron
72b122dbce Merge branch 'master' of github.com:webmin/webmin 2026-01-07 11:07:20 -08:00
Jamie Cameron
e9be9b1d53 Use my instead of local 2026-01-07 11:06:53 -08:00
Ilia Ross
7beeea2106 Fix to remove extra line
[no-build]
2026-01-07 19:13:17 +02:00
Jamie Cameron
27c5ad998e Merge branch 'master' of github.com:webmin/webmin 2026-01-07 07:48:28 -08:00
Jamie Cameron
474ca81d4a Fix parsing of attributes
https://sourceforge.net/p/webadmin/bugs/5659/
2026-01-07 07:48:21 -08:00
Ilia Ross
a63c92f77c Webmin version bump (use master workflow fixes)
*Note: The problem was that we didn't check the commit message correctly and didn't add the right flag to rebuild all non-core Webmin modules. Now it should work.
2026-01-07 14:40:52 +02:00
Jamie Cameron
db96a9fd09 Don't read output one byte at a time, especially when not needed 2026-01-06 21:58:25 -08:00
Jamie Cameron
198cc1c4d8 Clearing a hash while iterating over it seems unreliable 2026-01-05 21:35:47 -08:00
Jamie Cameron
4f681a2db0 Webmin version bump 2026-01-05 16:56:21 -08:00
Ilia Ross
ab46ec806f Fix action log clearing not to purge the previous logs on each save if the time option is not set 2026-01-05 19:29:54 +02:00
Jamie Cameron
b1fe988f57 Add an extra check for a manual config change that breaks the index 2026-01-04 13:35:24 -08:00
Jamie Cameron
94c0826499 Merge branch 'master' of github.com:webmin/webmin 2026-01-04 13:34:12 -08:00
Jamie Cameron
191d8a255c Add support for EC SSL cert and key file directives
https://github.com/webmin/webmin/issues/2597
2026-01-04 13:33:51 -08:00
Ilia Ross
d4ab826097 Add support for Ubuntu 26.04 2026-01-04 15:21:32 +02:00
Ilia Ross
a84c2af1d8 Merge pull request #2612 from hayden-t/patch-2
incorrect ssl_enforce setting for HSTS
2025-12-31 13:55:29 +02:00
Ilia Ross
476915850a Fix displaying correct IP version label
https://github.com/webmin/webmin/issues/2613
2025-12-31 12:46:07 +02:00
hayden-t
2510b985b1 incorrect ssl_enforce setting for HSTS
https://github.com/webmin/webmin/issues/2611
2025-12-30 13:54:39 +11:00
Ilia Ross
db4f5b5e9c Fix to skip uninstall when package is replaced by rename
*Note: This is to support clean upgrade and replacement of wbm-* → webmin-* modules
2025-12-28 19:39:52 +02:00
Jamie Cameron
f3dda46138 Merge branch 'master' of github.com:webmin/webmin 2025-12-28 08:02:42 -08:00
Jamie Cameron
7a278ad894 Add ed25519 as a key type
https://github.com/webmin/webmin/pull/2609
2025-12-28 08:02:34 -08:00
Jamie Cameron
179d36859a Merge pull request #2609 from hayden-t/patch-1
id_ed25519.pub support for authorized_keys update
2025-12-26 20:55:07 -08:00
hayden-t
eec14b6c1f id_ed25519.pub support for authorized_keys update 2025-12-27 09:05:17 +11:00
Ilia Ross
bc44562637 Add a new secret 2025-12-24 01:33:33 +02:00
Jamie Cameron
a55a9e142b Merge branch 'master' of github.com:webmin/webmin 2025-12-19 21:53:29 -08:00
Jamie Cameron
7911eaeae1 TLS options must be at the root level, not in the global block
https://github.com/webmin/webmin/issues/2597
2025-12-19 21:53:23 -08:00
Ilia Ross
a2cb8daaee Fix not to hide download errors 2025-12-20 04:10:29 +02:00
Jamie Cameron
c4dc034da7 Don't save allowed modules if fetching them fails 2025-12-18 17:47:58 -08:00
Jamie Cameron
8a8d1bc12f use has to be inside a string eval 2025-12-17 21:47:58 -08:00
Jamie Cameron
025ab50ecb Merge branch 'master' of github.com:webmin/webmin 2025-12-17 21:47:11 -08:00
Jamie Cameron
1f71aebe49 Don't run entropy generator if not needed
00~https://github.com/webmin/webmin/issues/260401~
2025-12-17 21:46:22 -08:00
Ilia Ross
03379b0052 Add ability to use correct driver depending on the database 2025-12-16 13:56:27 +02:00
Jamie Cameron
86a27eff18 Check for and use DBD::MariaBB as well
https://forum.virtualmin.com/t/webmin-2-6-not-available-yet/135883/45
2025-12-15 17:10:27 -08:00
Jamie Cameron
b338875039 Merge branch 'master' of github.com:webmin/webmin 2025-12-14 19:48:03 -08:00
Jamie Cameron
e88d16a888 UI for setting real name for from address
https://github.com/webmin/webmin/issues/2600
2025-12-14 15:13:56 -08:00
Ilia Ross
f0486443ae Fix to just recommend MariaDB Perl package 2025-12-14 19:58:56 +02:00
Ilia Ross
f55515fdb0 Revert "Fix to support installing missing Perl modules using package manager"
This reverts commit 9bce05f48a.
2025-12-14 19:53:42 +02:00
Ilia Ross
ebefe283fd Revert "Add FreeBSD package names"
This reverts commit f4e573278d.
2025-12-14 19:53:33 +02:00
Ilia Ross
d7c49a51c7 Fix to drop obsolete secret 2025-12-14 01:18:19 +02:00
Jamie Cameron
018ad0062b Add config option for real name part of from address
https://github.com/webmin/webmin/issues/2600
2025-12-13 10:17:49 -08:00
Ilia Ross
50075dd71d Fix pre-release repo label 2025-12-12 20:03:15 +02:00
Ilia Ross
f4e573278d Add FreeBSD package names 2025-12-11 09:44:35 +02:00
Ilia Ross
9bce05f48a Fix to support installing missing Perl modules using package manager 2025-12-10 18:57:14 +02:00
Ilia Ross
238756902b Fix to remove empty extra line 2025-12-09 02:43:42 +02:00
Ilia Ross
b597bfb0bd Fix stable name 2025-12-09 01:19:27 +02:00
Ilia Ross
00adb84d69 Revert "Fix to explicitly set no auto-reconnect"
This reverts commit 9ba627e461.
2025-12-09 00:53:48 +02:00
Ilia Ross
0cf0af85cd Update workflow 2025-12-07 20:50:36 +02:00
Ilia Ross
ae89844754 Fix repo name 2025-12-07 15:38:57 +02:00
Ilia Ross
9ba627e461 Fix to explicitly set no auto-reconnect
https://forum.virtualmin.com/t/webmin-2-6-not-available-yet/135883/10?u=ilia
2025-12-07 15:04:42 +02:00
Ilia Ross
bae6e39889 Fix to pad multiple keys with new line 2025-12-03 20:55:26 +02:00
Ilia Ross
5377098de3 Add support for installing multiple keys 2025-12-03 19:07:33 +02:00
Ilia Ross
48291845b4 Add support to set different key download server 2025-12-03 15:37:05 +02:00
Ilia Ross
36194ade8e Fix description consistency 2025-12-02 23:25:32 +02:00
Ilia Ross
d8e9ad116a Fix colors 2025-12-02 22:57:39 +02:00
Ilia Ross
4c5911887c Add support to configure repo pathname 2025-12-02 22:34:08 +02:00
Ilia Ross
392d3aceff Fix comments 2025-12-02 22:33:05 +02:00
Ilia Ross
15a3c91f9d Fix to keep repo name consistent 2025-12-02 22:31:17 +02:00
Ilia Ross
58c6d41295 Fix to correctly print downloaded and installed key name 2025-12-02 20:46:50 +02:00
Jamie Cameron
7b9bca5b67 Fix param for field being saved 2025-12-02 06:17:30 -08:00
Jamie Cameron
ba47e2a0d0 Fix selection of TLS cert/key fields 2025-12-02 05:35:08 -08:00
Jamie Cameron
3b06c4dccb Revert "Fux subs to use local variable for parameters #2597"
This reverts commit 1e6611ade5.
2025-12-02 05:32:33 -08:00
Ilia Ross
1e6611ade5 Fux subs to use local variable for parameters #2597 2025-12-01 14:45:24 +02:00
Jamie Cameron
dfa5ac8b21 Use more efficient code for if-undefined 2025-11-29 14:28:34 -08:00
Jamie Cameron
d7cf050d71 Merge branch 'master' of github.com:webmin/webmin 2025-11-29 14:26:59 -08:00
Jamie Cameron
7945a9f0e6 Don't globally change PGPASSFILE
https://github.com/webmin/webmin/issues/2592
2025-11-29 14:22:52 -08:00
Ilia Ross
6cd04c131c Fix to steal if needed and don't fail unless term wanted #2595 2025-11-29 22:19:02 +02:00
Jamie Cameron
9f4a4fadf7 Add tool tip about where to setup notifcation methods
https://github.com/webmin/webmin/issues/2594
2025-11-28 20:56:48 -08:00
Jamie Cameron
70c7ddf3bb New version bump 2025-11-23 17:53:04 -08:00
Ilia Ross
17764483dc Fix changelog missing link to the theme
[no-build]
2025-11-23 19:34:28 +02:00
Ilia Ross
cdc1c82e83 Update changelog for 2.610 2025-11-23 19:32:23 +02:00
Ilia Ross
d29e5aea99 Fix terminal normal font size
[no-build]
2025-11-23 15:30:53 +02:00
Ilia Ross
c3a2396986 Fix to exclude .github from builds 2025-11-20 20:30:41 +02:00
Ilia Ross
da37f364dd Fix to allow some hidden modules to be searchable
Like Virtualmin Virtual Servers, which has a separate switch in the menu, so we don't want it to show up as a regular link in the menu as well
2025-11-19 12:04:33 +02:00
Jamie Cameron
0342f06fc8 Remove dependency on IO::Pty for calling sudo
https://github.com/webmin/webmin/issues/2587
2025-11-18 21:04:48 -08:00
Jamie Cameron
ef05252413 Merge pull request #2589 from webmin/dev/pure-perl-openpty
Add support for using pure-Perl Linux fallback to open PTY
2025-11-18 15:00:30 -08:00
Ilia Ross
74f879dc2b Fix to move sub to Linux lib
https://github.com/webmin/webmin/pull/2589#discussion_r2539675817
2025-11-18 23:16:10 +02:00
Ilia Ross
94cbabea9e Add support for using pure-Perl Linux fallback to open PTY 2025-11-18 20:15:26 +02:00
Ilia Ross
2634afb859 Fix to displays 2FA better 2025-11-14 15:37:00 +02:00
Ilia Ross
4bf296447e Fix to drop js.map file
https://github.com/webmin/webmin/issues/2582#issuecomment-3529211242
2025-11-13 20:50:31 +02:00
Ilia Ross
cf458db765 Fix to open correct palette on the remote 2025-11-12 23:07:21 +02:00
Ilia Ross
af7cb99298 Update screenshots 2025-11-12 23:04:51 +02:00
248 changed files with 7988 additions and 2623 deletions

View File

@@ -6,15 +6,17 @@ on:
- master
release:
types:
- published
- prereleased
- released
jobs:
build:
uses: webmin/webmin-ci-cd/.github/workflows/master-workflow.yml@main
with:
build-type: package
project-name: webmin
project-name: ${{ github.event.repository.name }}
is-release: ${{ github.event_name == 'release' }}
is-prerelease: ${{ github.event.release.prerelease || false }}
secrets:
DEV_IP_ADDR: ${{ secrets.DEV_IP_ADDR }}
DEV_IP_KNOWN_HOSTS: ${{ secrets.DEV_IP_KNOWN_HOSTS }}
@@ -22,4 +24,4 @@ jobs:
DEV_UPLOAD_SSH_DIR: ${{ secrets.DEV_UPLOAD_SSH_DIR }}
PRERELEASE_UPLOAD_SSH_DIR: ${{ secrets.PRERELEASE_UPLOAD_SSH_DIR }}
DEV_SSH_PRV_KEY: ${{ secrets.DEV_SSH_PRV_KEY }}
DEV_SIGN_BUILD_REPOS_CMD: ${{ secrets.DEV_SIGN_BUILD_REPOS_CMD }}
ALL_GPG_PH2: ${{ secrets.ALL_GPG_PH2 }}

View File

@@ -1,5 +1,49 @@
## Changelog
#### 2.621 (January 25, 2026)
* Fix to prevent NAT from dropping idle RPC sessions during long transfers
* Fix to improve the message when socket authentication is used in the MySQL/MariaDB module
* Fix to make upload tracking work correctly in all situations and on all systems
* Fix to correctly display the PHP version in the PHP Configuration module when managing packages
* Update Xterm.js to the latest version with lots of improvements and fixes
* Update Authentic theme to the latest version with various improvements and fixes:
* Fix the support for the cloned Terminal module
* Fix error handling for file uploads when the user is out of quota or the system is out of disk space in the File Manager module
* Fix to stop loading full file into memory for upload check to prevent memory leak on large uploads in the File Manager module
* Fix to permanently save the state of the navigation menu and right-side slider when toggled
#### 2.620 (January 9, 2026)
* Add ability to use correct driver depending on the database in MySQL/MariaDB module
* Add improvements to BIND DNS module for better key management
* Add support for Ubuntu 26.04 development preview
* Add a config option to increase the RPC timeout
* Add support for EC SSL certificate and key in the ProFTPd module
* Add support for using `gpart` in FreeBSD disk management module
* Add support for Ed25519 public key in User and Groups module
* Fix RPC session timeout during large file transfers
* Fix selection and configuration of TLS certificate and key in the ProFTPd module
* Update Authentic theme to the latest version with various improvements and fixes:
* Add support for multiple scrollable tabs in the File Manager
* Fix displaying of the right-side toolbar in File Manager when using Safari
* Fix to print menu separator when no virtual servers are added yet in Virtualmin
* Fix bugs in white palette
* Fix exported file name in data tables
#### 2.610 (November 23, 2025)
* Fix to drop dependency on `IO::Pty` Perl module
* Fix `virtual-server` module server-side search to work correctly
* Update the Authentic theme to the latest version with various improvements and fixes:
- Add a range slider to adjust content page margins more precisely
- Add an option to enable rounded corners for content page
- Add more customization options for pie charts
- Fix to increase clickable area for checkboxes in File Manager
- Fix to correct rotation of pin and unpin button for right side slider
- Fix color of selected items in the multiselect dropdown
- Fix to improve the visibility of disabled checkboxes
- Fix to send saved params in the post body when saving theme configuration
[More details...](https://github.com/webmin/authentic-theme/releases/tag/26.20)
#### 2.600 (November 9, 2025)
* Add an options to enable the slow query log in the MySQL/MariaDB module [#2560](https://github.com/webmin/webmin/issues/2560)
* Add ability to install multiple PHP extensions at once in the PHP Configuration module
@@ -256,7 +300,7 @@
* Fix to using the `qrencode` command to generate QR codes locally instead of the remote Google Chart API
* Fix a number of various other issues
#### 2.105 (November 09, 2023)
#### 2.105 (November 9, 2023)
* Fix param to read only headers [sourceforge.net/usermin-bugs#501](https://sourceforge.net/p/webadmin/usermin-bugs/501/)
* Fix not to set `reuse` flag on initial Let's Encrypt request
* Fix to correctly escape mail file names upon deletion
@@ -271,7 +315,7 @@
* Fix the absent init script for legacy systems after the initial installation
* Update the Authentic theme to the latest version with various fixes and improvements
#### 2.103 (October 08, 2023)
#### 2.103 (October 8, 2023)
* Add support for hostname detection using `hostnamectl` command
* Add support for other ACME services
* Add ability to hide dotfiles in File Manager [#1578](https://github.com/webmin/authentic-theme/issues/1578)
@@ -320,7 +364,7 @@
* Fix clearing packages caches before checking for updates in status collection #1863
* Update the Authentic theme to the latest version
#### 2.020 (March 08, 2023)
#### 2.020 (March 8, 2023)
* Add full locale support
* Add slave zone file format option in BIND DNS module
* Add support for editing ACLs in File Manager
@@ -490,10 +534,10 @@ This release updates the built-in Let's Encrypt client, adds support for creatin
#### Version 1.930 (August 18, 2019)
These updates fix a [security vulnerability](http://webmin.com/security.html) and should be installed IMMEDIATELY by all users. Although it is not exploitable in a Webmin install with the default configuration, upgrading is strongly recommended.
#### Version 1.920 (July 04, 2019)
#### Version 1.920 (July 4, 2019)
This update includes the latest theme version, translation updates, the ability to disable hosts file entries, easier monitoring of bootup actions, and a bunch of bugfixes.
#### Version 1.910 (May 09, 2019)
#### Version 1.910 (May 9, 2019)
This release includes theme and translation updates, a page for editing package repositories, cron and status module improvements, and a bunch of other bugfixes and small improvements.
#### Version 1.900 (November 19, 2018)
@@ -505,7 +549,7 @@ This version includes Ubuntu 18 network config support, translation updates, mul
#### Version 1.880 (March 16, 2018)
This version includes German, Catalan and Bulgarian translation updates, a new version of the Authentic theme, support for directly editing the MySQL and PostgreSQL config files, Let's Encrypt bugfixes, more control over system status email notifications, and more.
#### Version 1.870 (December 08, 2018)
#### Version 1.870 (December 8, 2018)
This release includes many translation updates, fixes for Let's Encrypt support, UI cleanups, and most importantly a new major version of the Authentic theme.
#### Version 1.860 (October 10, 2017)
@@ -514,7 +558,7 @@ This release includes Let's Encrypt DNS fixes, Majordomo module improvements, XS
#### Version 1.850 (June 28, 2017)
This release includes Let's Encrypt fixes, Majordomo module improvements, FirewallD forwarding support, translation updates, an update to the Authentic theme, and a bunch of other bugfixes.
#### Version 1.840 (May 08, 2017)
#### Version 1.840 (May 8, 2017)
This major release includes a large theme update, XSS security fixes, per-domain SSL cert support, thin-provisioned LVM support, Let's Encrypt improvements, translation updates, and the usual gang of bugfixes. Also available is Usermin 1.710, which contains many of the same updates.
#### Version 1.830 (December 29, 2016)

View File

@@ -19,11 +19,11 @@
**Webmin** is a web-based system administration tool for Unix-like servers, and services with about _1,000,000_ yearly installations worldwide. Using it, it is possible to configure operating system internals, such as users, disk quotas, services or configuration files, as well as modify, and control open-source apps, such as BIND DNS Server, Apache HTTP Server, PHP, MySQL, and many more.
<p align="center">
<a href="https://webmin.com/screenshots/#gh-light-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://user-images.githubusercontent.com/4426533/218264253-c08fb45a-8d75-44bf-93b3-37a2ecae3d20.png">
<a href="https://webmin.com/screenshots/?theme=light#gh-light-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/a01d0a78-4130-4665-9284-814955ae1c97">
</a>
<a href="https://webmin.com/screenshots/#gh-dark-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://user-images.githubusercontent.com/4426533/218265232-31140aa6-ada1-4019-bd75-04240aeabc83.png">
<a href="https://webmin.com/screenshots/?theme=dark#gh-dark-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/da4b90a0-c002-4e10-8b34-5acb251bbec9">
</a>
</p>

File diff suppressed because one or more lines are too long

View File

@@ -291,7 +291,13 @@ return &ui_checkbox("d", $user->{'name'}, "", 0).
($ro ? "<b>" : "").
&ui_link("$cgi?$param=".&urlize($user->{'name'}),
$user->{'name'}).
($user->{'twofactor_id'} ? "*" : "").
($user->{'twofactor_id'}
? &ui_tag('sup', '⚷',
{ title => $text{'index_twofactor_enabled'},
class => 'twofactor-enabled-icon',
style => 'font-size: 11px; margin-left: 5px; cursor: default;'.
'display: inline-block; transform: rotate(90deg);' } )
: "").
($ro ? "</b>" : "").
($lck ? "</i>" : "");
}

View File

@@ -6,6 +6,7 @@ index_screate=Create a new safe user.
index_convert=Convert Unix To Webmin Users
index_cert=Request an SSL Certificate
index_twofactor=Two-Factor Authentication
index_twofactor_enabled=Two-factor authentication is enabled for this user
index_certmsg=Click this button to request an SSL certificate that will allow you to securely login to Webmin without having to enter a username and password.
index_return=user list
index_none=None
@@ -513,9 +514,18 @@ twofactor_enable=Enroll For Two-Factor Authentication
twofactor_header=Two-factor authentication enrollment details
twofactor_enrolling=Enrolling for two-factor authentication with provider $1 ..
twofactor_failed=.. enrollment failed : $1
twofactor_done=.. complete. Your ID with this provider is <tt>$1</tt>.
twofactor_done=.. completed, with ID <tt>$1</tt>
twofactor_setup=Two-factor authentication has not been enabled on this system yet, but can be turned on using the <a href='$1'>Webmin Configuration</a> module.
twofactor_ebutton=No button clicked!
twofactor_testdesc=Before logging out, you can test your new two-factor authentication setup here by entering a token. If for some reason it doesn't work, turn off two-factor authentication and try setting it up again.
twofactor_testfield=Two-factor token
twofactor_test=Validate Token
twofactor_terr=Failed to test two-factor setup
twofactor_etestuser=Login does not have two-factor enabled!
twofactor_testing=Testing two-factor validation with $1 ..
twofactor_testfailed=.. test failed! Maybe the wrong token was entered, or your authentication app has not been configured correctly?
twofactor_testok=.. test passed! You can now safely login using two-factor authentication.
twofactor_testdis=Disable Two-Factor Now
forgot_title=Send Password Reset Link
forgot_err=Failed to send password reset link

View File

@@ -55,7 +55,7 @@ if ($in{'enable'}) {
my $mfunc = "webmin::message_twofactor_".
$miniserv{'twofactor_provider'};
if (defined(&{\&{$mfunc}})) {
print &{\&{$mfunc}}($user);
print "<p></p>".&{\&{$mfunc}}($user);
}
# Save user
@@ -65,6 +65,15 @@ if ($in{'enable'}) {
&webmin_log("twofactor", "user", $user->{'name'},
{ 'provider' => $user->{'twofactor_provider'},
'id' => $user->{'twofactor_id'} });
# Show a test form, so the user can validate
print &ui_form_start("test_twofactor.cgi");
print $text{'twofactor_testdesc'},"<p>\n";
print "$text{'twofactor_testfield'}&nbsp;\n",
&ui_textbox("test", undef, 12),"\n";
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
print "<p>\n";
print &ui_form_end([ [ undef, $text{'twofactor_test'} ] ]);
}
&ui_print_footer("", $text{'index_return'});

47
acl/test_twofactor.cgi Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/local/bin/perl
# Validate a user-supplied two-factor token
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './acl-lib.pl';
our (%in, %text, %access, $base_remote_user);
&foreign_require("webmin");
&error_setup($text{'twofactor_terr'});
&ReadParse();
# Get the user
my @users = &list_users();
my $user;
if ($in{'user'}) {
&can_edit_user($in{'user'}) || &error($text{'edit_euser'});
($user) = grep { $_->{'name'} eq $in{'user'} } @users;
}
else {
($user) = grep { $_->{'name'} eq $base_remote_user } @users;
}
$user || &error($text{'twofactor_euser'});
$user->{'twofactor_provider'} || &error($text{'twofactor_etestuser'});
my @provs = &webmin::list_twofactor_providers();
my ($prov) = grep { $_->[0] eq $user->{'twofactor_provider'} } @provs;
# Call the validation function
&ui_print_header(undef, $text{'twofactor_title'}, "");
print &text('twofactor_testing', $prov->[1]),"<br>\n";
my $func = "webmin::validate_twofactor_".$user->{'twofactor_provider'};
my $err = &{\&{$func}}($user->{'twofactor_id'}, $in{'test'},
$user->{'twofactor_apikey'});
if ($err) {
print &text('twofactor_testfailed', $err),"<p>\n";
print &ui_form_start("save_twofactor.cgi");
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
print &ui_form_end([ [ "disable", $text{'twofactor_testdis'} ] ]);
}
else {
print $text{'twofactor_testok'},"<p>\n";
}
&ui_print_footer("", $text{'index_return'});

View File

@@ -12,7 +12,7 @@ $conf = &get_config();
&indexof($in{'file'}, @files) >= 0 || &error($text{'manual_efile'});
$temp = &transname();
&execute_command("cp ".quotemeta($in{'file'})." $temp");
&execute_command("cp ".quotemeta($in{'file'})." ".quotemeta($temp));
$in{'data'} =~ s/\r//g;
&lock_file($in{'file'});
&open_tempfile(FILE, ">$in{'file'}");
@@ -22,7 +22,7 @@ $in{'data'} =~ s/\r//g;
if ($config{'test_manual'}) {
$err = &test_config();
if ($err) {
&execute_command("mv $temp '$in{'file'}'");
&execute_command("mv ".quotemeta($temp)." ".quotemeta($in{'file'}));
&error(&text('manual_etest', "<pre>$err</pre>"));
}
}
@@ -30,4 +30,3 @@ unlink($temp);
&format_config_file($in{'file'});
&webmin_log("manual", undef, undef, { 'file' => $in{'file'} });
&redirect("index.cgi?mode=global");

View File

@@ -812,9 +812,16 @@ foreach my $d (@$conf) {
# Convert a relative path to being under the server root
sub server_root
{
if (!$_[0]) { return undef; }
elsif ($_[0] =~ /^\//) { return $_[0]; }
else { return "$config{'httpd_dir'}/$_[0]"; }
my ($path) = @_;
if (!$path) {
return undef;
}
elsif ($path =~ /^\//) {
return $path;
}
else {
return "$config{'httpd_dir'}/$path";
}
}
sub dump_config
@@ -1432,7 +1439,7 @@ sub allowed_auth_file
local $_;
return 1 if ($access{'dir'} eq '/');
return 0 if ($_[0] =~ /\.\./);
local $f = &server_root($_[0], &get_config());
local $f = &server_root($_[0]);
return 0 if (-l $f && !&allowed_auth_file(readlink($f)));
local $l = length($access{'dir'});
return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
@@ -1442,7 +1449,7 @@ return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
# Returns 1 if the directory in some path exists
sub directory_exists
{
local $path = &server_root($_[0], &get_config());
local $path = &server_root($_[0]);
if ($path =~ /^(\S*\/)([^\/]+)$/) {
return -d $1;
}
@@ -1618,7 +1625,7 @@ local $conf = &get_config();
local $pidfilestr = &find_directive_struct("PidFile", $conf);
local $pidfile = $pidfilestr ? $pidfilestr->{'words'}->[0]
: "logs/httpd.pid";
return &server_root($pidfile, $conf);
return &server_root($pidfile);
}
# restart_apache()
@@ -1803,8 +1810,8 @@ if (!&is_apache_running()) {
return $text{'start_eunknown'};
}
else {
$errorlog = &server_root($errorlog, $conf);
$out = `tail -5 $errorlog`;
$errorlog = &server_root($errorlog);
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
return "$text{'start_eafter'} : <pre>$out</pre>";
}
}
@@ -1820,7 +1827,7 @@ local $conf = &get_config();
local $errorlogstr = &find_directive_struct("ErrorLog", $conf);
local $errorlog = $errorlogstr ? $errorlogstr->{'words'}->[0]
: "logs/error_log";
$errorlog = &server_root($errorlog, $conf);
$errorlog = &server_root($errorlog);
return $errorlog;
}
@@ -2297,4 +2304,3 @@ sub clear_apache_modules_cache
}
1;

View File

@@ -14,23 +14,23 @@ push(@rv, map { $_->{'file'} } @$conf);
# Add mime types file
local $mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
if ($mfile) {
push(@rv, &server_root($mfile, $conf));
push(@rv, &server_root($mfile));
}
# Add mime magic file
local $magic = &find_directive("MimeMagicFile", $conf);
if ($magic) {
push(@rv, &server_root($magic, $conf));
push(@rv, &server_root($magic));
}
# Add all auth files
local $auth;
foreach $auth (&find_all_directives($conf, "AuthUserFile"),
&find_all_directives($conf, "AuthGroupFile")) {
push(@rv, &server_root($auth, $conf));
push(@rv, &server_root($auth));
}
return &unique(@rv);

View File

@@ -119,8 +119,10 @@ elsif ($in{'fmode'} == 1) {
else {
# Use a user-specified file
$f = $in{'file'};
&allowed_auth_file($f) ||
&error(&text('cvirt_efile', &html_escape($f), $!));
}
-r $f || open(FILE, ">>$f") || &error(&text('cvirt_efile', &html_escape($f), $!));
-r $f || open(FILE, ">>", $f) || &error(&text('cvirt_efile', &html_escape($f), $!));
close(FILE);
&lock_apache_files();

View File

@@ -22,9 +22,9 @@ print &ui_form_end([ [ "", $text{'save'} ] ]);
if ($in{'type'} == 6) {
$mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
$mfile = &server_root($mfile, $conf);
if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
$mfile = &server_root($mfile);
print &ui_hr();
print &ui_subheading($text{'global_mime'});
print "$text{'global_mimedesc'}<p>\n";

View File

@@ -5,9 +5,19 @@
require './apache-lib.pl';
&ReadParse();
$access{'global'}==1 || &error($text{'mime_ecannot'});
# Validate that the file parameter matches the actual MIME types file
$conf = &get_config();
$mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
$mfile = &server_root($mfile);
$in{'file'} eq $mfile || &error($text{'mime_ecannot'});
if (defined($in{'line'})) {
&ui_print_header(undef, $text{'mime_edit'}, "");
open(MIME, "<$in{'file'}");
open(MIME, "<", $in{'file'});
for($i=0; $i<=$in{'line'}; $i++) {
$line = <MIME>;
}

View File

@@ -161,6 +161,7 @@ manual_efile=Invalid Apache config file
manual_etest=Configuration file error detected : $1
manual_editfile=Edit config file:
manual_switch=Edit
manual_evirt=Virtual host could not be found after manual changes - maybe the ServerName was changed?
dir_title=Per-Directory Options
dir_proxyall=All proxy requests

View File

@@ -11,7 +11,7 @@ $conf = &get_config();
&error(&text('authg_ecannot', $in{'file'}));
$desc = &text('authg_header', "<tt>$in{'file'}</tt>");
&ui_print_header($desc, $text{'authg_title'}, "");
$f = &server_root($in{'file'}, $conf);
$f = &server_root($in{'file'});
@groups = sort { $a->{'name'} cmp $b->{'name'} } &list_authgroups($in{'file'});
if (@groups) {

View File

@@ -11,7 +11,7 @@ $conf = &get_config();
&error(&text('authu_ecannot', $in{'file'}));
$desc = &text('authu_header', "<tt>$in{'file'}</tt>");
&ui_print_header($desc, $text{'authu_title'}, "");
$f = &server_root($in{'file'}, $conf);
$f = &server_root($in{'file'});
@users = sort { $a cmp $b } &list_authusers($f);
if (@users) {

View File

@@ -73,6 +73,14 @@ if ($config{'test_manual'}) {
&error(&text('manual_etest',
"<pre>".&html_escape($err)."</pre>"));
}
if (defined($in{'virt'}) && !defined($in{'idx'})) {
undef(@get_config_cache);
($conf, $v) = &get_virtual_config($in{'virt'});
if (!$v) {
&copy_source_dest($temp, $file);
&error($text{'manual_evirt'});
}
}
}
unlink($temp);
&unlock_file($file);

View File

@@ -34,11 +34,10 @@ if (!$running) {
&error($text{'restart_eunknown'});
}
else {
$errorlog = &server_root($errorlog, $conf);
$out = `tail -5 $errorlog`;
$errorlog = &server_root($errorlog);
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
&error("<pre>$out</pre>");
}
}
&webmin_log("apply");
&redirect($in{'redir'});

View File

@@ -6,13 +6,22 @@ require './apache-lib.pl';
&ReadParse();
$access{'global'}==1 || &error($text{'mime_ecannot'});
# Validate that the file parameter matches the actual MIME types file
$conf = &get_config();
$mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
$mfile = &server_root($mfile);
$in{'file'} eq $mfile || &error($text{'mime_ecannot'});
&error_setup($text{'mime_err'});
if ($in{'type'} !~ /^(\S+)\/(\S+)$/) {
&error(&text('mime_etype', $in{'type'}));
}
&lock_file($in{'file'});
open(MIME, "<$in{'file'}");
open(MIME, "<", $in{'file'});
@mime = <MIME>;
close(MIME);
$line = "$in{'type'} ".join(" ", split(/\s+/, $in{'exts'}))."\n";

View File

@@ -1,408 +1,435 @@
#!/usr/bin/env perl
# Webmin CLI - Allows performing a variety of common Webmin-related
# functions on the command line.
use strict;
use warnings;
BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Color'; }
use 5.010; # Version in CentOS 6
use Getopt::Long qw(:config permute pass_through);
use 5.010;
use Getopt::Long qw(:config no_ignore_case permute pass_through);
use Term::ANSIColor qw(:constants);
use Pod::Usage;
# Check if root
if ($> != 0) {
die BRIGHT_RED, "Error: ", RESET, BRIGHT_YELLOW,"webmin", RESET,
" command must be run as root\n";
exit 1;
}
die BRIGHT_RED,
"Error: ", RESET, BRIGHT_YELLOW,"webmin", RESET,
" command must be run as root\n";
exit 1;
}
my $a0 = $ARGV[0];
sub main {
my ( %opt, $subcmd );
GetOptions(
'help|h' => \$opt{'help'},
'config|c=s' => \$opt{'config'},
'list-commands|l' => \$opt{'list'},
'describe|d' => \$opt{'describe'},
'man|m' => \$opt{'man'},
'version|v' => \$opt{'version'},
'versions' => \$opt{'versions'},
'<>' => sub {
# Handle unrecognized options, inc. subcommands.
my($arg) = @_;
if ($arg =~ m{^-}) {
say "Usage error: Unknown option $arg.";
pod2usage(0);
} else {
# It must be a subcommand.
$subcmd = $arg;
die "!FINISH";
}
}
);
sub main
{
my ( %opt, $subcmd );
GetOptions(
'help|h' => \$opt{'help'},
'config|c=s' => \$opt{'config'},
'list-commands|l' => \$opt{'list'},
'describe|d' => \$opt{'describe'},
'man|m' => \$opt{'man'},
'version|v' => \$opt{'version'},
'versions|V' => \$opt{'versions'},
'<>' => sub {
# Handle unrecognized options, inc. subcommands.
my($arg) = @_;
if ($arg =~ m{^-}) {
say "Usage error: Unknown option $arg.";
pod2usage(0);
}
else {
# It must be a subcommand.
$subcmd = $arg;
die "!FINISH";
}
}
);
# Set defaults
$opt{'config'} ||= "/etc/webmin";
$opt{'commands'} = $a0;
# Load libs
loadlibs(\%opt);
# Set defaults
$opt{'config'} ||= "/etc/webmin";
$opt{'commands'} = $a0;
my @remain = @ARGV;
# List commands?
if ($opt{'list'}) {
list_commands(\%opt);
exit 0;
} elsif ($opt{'version'} || $opt{'versions'}) {
# Load libs
my $ver_checked = sub {
my ($ver_remote, $ver_curr) = @_;
if ($ver_remote && $ver_curr &&
compare_version_numbers($ver_remote, $ver_curr) > 0 ) {
return (BRIGHT_RED, $ver_curr, RESET, DARK, " (" . RESET, BRIGHT_GREEN, $ver_remote, RESET, DARK . " is available)", RESET);
} else {
return GREEN, $ver_curr, RESET;
}
};
my $print_mod_vers = sub {
my ($module_type, $modules_list, $prod_root, $prod_ver, $versions_remote_local) = @_;
my @minfo;
if (ref($modules_list)) {
my $head;
my @modules_list = sort(@{$modules_list});
foreach my $mod (@modules_list) {
my %mod_info;
read_file($mod, \%mod_info);
my $mod_ver = $mod_info{'version_actual'} || $mod_info{'version'};
my $mod_desc = $mod_info{'desc'};
if ($mod_ver && $prod_ver && $mod_desc && $prod_ver !~ /^$mod_ver/) {
say CYAN, " $module_type: ", RESET if (!$head++);
my ($mod_dir) = $mod =~ m/$prod_root\/(.*?)\//;
push(@minfo, {'desc' => $mod_desc, 'ver' => $mod_ver, 'dir' => $mod_dir});
}
}
@minfo = sort { $a->{'desc'} cmp $b->{'desc'} } @minfo;
foreach my $mod (@minfo) {
say " $mod->{'desc'}: " , &$ver_checked($versions_remote_local->{$mod->{'dir'}}, $mod->{'ver'}), DARK " [$mod->{'dir'}]", RESET;
}
}
};
# Load libs
loadlibs(\%opt);
my $root = root($opt{'config'});
if ($root && -d $root) {
require("$root/web-lib-funcs.pl");
# Try to get remote versions first
my %versions_remote;
if ($opt{'versions'}) {
my ($latest_known_versions_remote, $latest_known_versions_remote_error);
http_download("virtualmin.com", 443, '/software-latest',
\$latest_known_versions_remote, \$latest_known_versions_remote_error,
undef, 1, undef, undef, 5);
if ($latest_known_versions_remote &&
!$latest_known_versions_remote_error) {
%versions_remote = map {
my ($k, $v) = split(/=/, $_, 2);
defined($v) ? ($k => $v) : ();
} split(/\n/, $latest_known_versions_remote);
} elsif ($latest_known_versions_remote_error) {
say BRIGHT_YELLOW, "Warning: ", RESET, "Cannot fetch remote packages versions list - $latest_known_versions_remote_error";
}
}
# Get Webmin version installed
my $ver1 = "$root/version";
my $ver2 = "$opt{'config'}/version";
my $ver = read_file_contents($ver1) || read_file_contents($ver2);
my $verrel_file = "$root/release";
my $verrel = -r $verrel_file ? read_file_contents($verrel_file) : "";
if ($verrel) {
$verrel = ":@{[trim($verrel)]}";
}
$ver = trim($ver);
if ($ver) {
if ($opt{'version'}) {
say "$ver$verrel";
exit 0;
} else {
say CYAN, "Webmin: ", RESET, &$ver_checked($versions_remote{'webmin'}, "$ver$verrel"), DARK " [$root]", RESET;
}
} else {
say RED, "Error: ", RESET, "Cannot determine Webmin version";
exit 1;
}
# Get other Webmin themes/modules versions if available
my ($dir, @themes, @mods);
if (opendir($dir, $root)) {
while (my $file = readdir($dir)) {
my $theme_info_file = "$root/$file/theme.info";
push(@themes, $theme_info_file)
if (-r $theme_info_file);
my @remain = @ARGV;
# List commands?
if ($opt{'list'}) {
list_commands(\%opt);
exit 0;
}
elsif ($opt{'version'} || $opt{'versions'}) {
# Load libs
my $print_mod_vers = sub {
my ($module_type, $modules_list, $prod_root, $prod_ver) = @_;
return if (!ref($modules_list));
# Gather module info
my @mods;
foreach my $mod (@{$modules_list}) {
my %mi;
read_file($mod, \%mi);
my $ver = $mi{'version'};
my ($dir) = $mod =~ m/$prod_root\/(.*?)\//;
next if (!$ver || !$mi{'desc'} || !$dir);
next if ($prod_ver =~ /^\Q$ver\E/);
push(@mods, { desc => $mi{'desc'}, ver => $ver,
dir => $dir });
}
# Print sorted by description
my $head;
foreach my $m (sort { $a->{'desc'} cmp $b->{'desc'} } @mods) {
my $mod_ver = $m->{'ver'};
if (-r "$prod_root/$m->{'dir'}/module.info") {
eval { no warnings 'once';
local $main::error_must_die = 1;
&foreign_require($m->{'dir'}) };
# Get module edition if available
my $ed;
$ed = eval { &foreign_call(
$m->{'dir'},
"get_module_edition")
} if (&foreign_defined($m->{'dir'},
"get_module_edition"));
$mod_ver .= " $ed" if ($ed);
}
say CYAN, " $module_type: ", RESET if (!$head++);
say " $m->{'desc'}: ", GREEN, $mod_ver, RESET,
DARK " [$m->{'dir'}]", RESET;
}
};
my $mod_info_file = "$root/$file/module.info";
push(@mods, $mod_info_file)
if (-r $mod_info_file);
}
}
closedir($dir);
&$print_mod_vers('Themes', \@themes, $root, $ver, \%versions_remote);
&$print_mod_vers('Modules', \@mods, $root, $ver, \%versions_remote);
my $root = root($opt{'config'});
if ($root && -d $root) {
$ENV{'WEBMIN_CONFIG'} = $opt{'config'};
no warnings 'once';
@main::root_directories = ($root);
$main::root_directory = $root;
*unique = sub { my %seen; grep { !$seen{$_}++ } @_ }
if (!defined(&unique));
use warnings;
require("$root/web-lib-funcs.pl");
# Get Webmin version installed
my $ver1 = "$root/version";
my $ver2 = "$opt{'config'}/version";
my $ver = read_file_contents($ver1) ||
read_file_contents($ver2);
my $verrel_file = "$root/release";
my $verrel = -r $verrel_file
? read_file_contents($verrel_file) : "";
if ($verrel) {
$verrel = ":@{[trim($verrel)]}";
}
$ver = trim($ver);
if ($ver) {
if ($opt{'version'}) {
say "$ver$verrel";
exit 0;
}
else {
say CYAN, "Webmin: ", RESET, GREEN,
"$ver$verrel", RESET,
DARK " [$root]", RESET;
}
}
else {
say RED, "Error: ", RESET,
"Cannot determine Webmin version";
exit 1;
}
# Get other Webmin themes/modules versions if available
my ($dir, @themes, @mods);
if (opendir($dir, $root)) {
while (my $file = readdir($dir)) {
my $theme_info_file =
"$root/$file/theme.info";
push(@themes, $theme_info_file)
if (-r $theme_info_file);
my $mod_info_file = "$root/$file/module.info";
push(@mods, $mod_info_file)
if (-r $mod_info_file);
}
}
closedir($dir);
&$print_mod_vers('Themes', \@themes, $root, $ver);
&$print_mod_vers('Modules', \@mods, $root, $ver);
# Check for Usermin
my $wmumconfig = "$opt{'config'}/usermin/config";
if (-r $wmumconfig) {
my %wmumconfig;
read_file($wmumconfig, \%wmumconfig);
# Check for Usermin
my $wmumconfig = "$opt{'config'}/usermin/config";
if (-r $wmumconfig) {
my %wmumconfig;
read_file($wmumconfig, \%wmumconfig);
# Usermin config dir
$wmumconfig = $wmumconfig{'usermin_dir'};
if ($wmumconfig) {
my %uminiserv;
read_file("$wmumconfig/miniserv.conf", \%uminiserv);
my $uroot = $uminiserv{'root'};
# Usermin config dir
$wmumconfig = $wmumconfig{'usermin_dir'};
if ($wmumconfig) {
my %uminiserv;
read_file("$wmumconfig/miniserv.conf",
\%uminiserv);
my $uroot = $uminiserv{'root'};
# Get Usermin version installed
if ($uroot && -d $uroot) {
my $uver1 = "$uroot/version";
my $uver2 = "$wmumconfig/version";
my $uver = read_file_contents($uver1) || read_file_contents($uver2);
my $uverrel_file = "$uroot/release";
my $uverrel = -r $uverrel_file ? read_file_contents($uverrel_file) : "";
if ($uverrel) {
$uverrel = ":@{[trim($uverrel)]}";
}
$uver = trim($uver) . $uverrel;
if ($uver) {
say CYAN, "Usermin: ", RESET, &$ver_checked($versions_remote{'usermin'}, $uver), DARK " [$uroot]", RESET;
my ($udir, @uthemes, @umods);
if (opendir($udir, "$uroot")) {
while (my $file = readdir($udir)) {
my $theme_info_file = "$uroot/$file/theme.info";
push(@uthemes, $theme_info_file)
if (-r $theme_info_file);
# Get Usermin version installed
if ($uroot && -d $uroot) {
my $uver1 = "$uroot/version";
my $uver2 = "$wmumconfig/version";
my $uver = read_file_contents($uver1) ||
read_file_contents($uver2);
my $uverrel_file = "$uroot/release";
my $uverrel = -r $uverrel_file
? read_file_contents($uverrel_file) : "";
$uverrel = ":@{[trim($uverrel)]}" if ($uverrel);
$uver = trim($uver) . $uverrel;
if ($uver) {
say CYAN, "Usermin: ", RESET, GREEN, $uver, RESET, DARK " [$uroot]", RESET;
my ($udir, @uthemes, @umods);
if (opendir($udir, "$uroot")) {
while (my $file = readdir($udir)) {
my $theme_info_file = "$uroot/$file/theme.info";
push(@uthemes, $theme_info_file)
if (-r $theme_info_file);
my $mod_info_file = "$uroot/$file/module.info";
push(@umods, $mod_info_file)
if (-r $mod_info_file);
my $mod_info_file = "$uroot/$file/module.info";
push(@umods, $mod_info_file)
if (-r $mod_info_file);
}
}
closedir($udir);
&$print_mod_vers('Themes', \@uthemes, $uroot, $uver, \%versions_remote);
&$print_mod_vers('Modules', \@umods, $uroot, $uver, \%versions_remote);
}
}
}
}
}
}
}
closedir($udir);
&$print_mod_vers('Themes', \@uthemes, $uroot, $uver);
&$print_mod_vers('Modules', \@umods, $uroot, $uver);
}
}
}
}
}
exit 0;
}
elsif ($opt{'man'} || $opt{'help'} || !defined($remain[0])) {
# Show the full manual page
man_command(\%opt, $subcmd);
exit 0;
}
elsif ($subcmd) {
run_command( \%opt, $subcmd, \@remain );
}
exit 0;
} elsif ($opt{'man'} || $opt{'help'} || !defined($remain[0])) {
# Show the full manual page
man_command(\%opt, $subcmd);
exit 0;
} elsif ($subcmd) {
run_command( \%opt, $subcmd, \@remain );
}
exit 0;
exit 0;
}
exit main( \@ARGV ) if !caller(0);
# run_command - Run a subcommand
# $optref is a reference to an options object passed down from global options
# like --help or a --config path.
sub run_command {
my ( $optref, $subcmd, $remainref ) = @_;
sub run_command
{
my ( $optref, $subcmd, $remainref ) = @_;
# Load libs
loadlibs($optref);
# Load libs
loadlibs($optref);
# Figure out the Webmin root directory
my $root = root($optref->{'config'});
# Figure out the Webmin root directory
my $root = root($optref->{'config'});
my (@commands) = list_commands($optref);
if (! grep( /^$subcmd$/, @commands ) ) {
say RED, "Error: ", RESET, "Command \`$subcmd\` doesn't exist", RESET;
exit 1;
}
my (@commands) = list_commands($optref);
if (! grep( /^$subcmd$/, @commands ) ) {
say RED, "Error: ", RESET, "Command \`$subcmd\` doesn't exist", RESET;
exit 1;
}
my $command_path = get_command_path($root, $subcmd, $optref);
# Merge the options
# Only handling config, right now...
# XXX Should we do this with libraries instead of commands?
# Maybe detect .pm for that possibility.
my @allopts = ("--config", "$optref->{'config'}", @$remainref);
# Run that binch
system($command_path, @allopts);
# Try to exit with the passed through exit code (rarely used, but
# why not?)
if ($? == -1) {
say RED, "Error: ", RESET, "Failed to execute \`$command_path\`: $!";
exit 1;
} else {
exit $? >> 8;
}
my $command_path = get_command_path($root, $subcmd, $optref);
# Merge the options
my @allopts = ("--config", "$optref->{'config'}", @$remainref);
# Run
system($command_path, @allopts);
# Try to exit with the passed through exit code (rarely used, but why not?)
if ($? == -1) {
say RED, "Error: ", RESET, "Failed to execute \`$command_path\`: $!";
exit 1;
}
else {
exit $? >> 8;
}
}
sub get_command_path {
my ($root, $subcmd, $optref) = @_;
sub get_command_path
{
my ($root, $subcmd, $optref) = @_;
# Load libs
loadlibs($optref);
# Load libs
loadlibs($optref);
# Check for a root-level command (in "$root/bin")
my $command_path;
if ($subcmd) {
$command_path = File::Spec->catfile($root, 'bin', $subcmd);
} else {
$command_path = File::Spec->catfile($root, 'bin', 'webmin');
}
my $module_name;
my $command;
if ( -x $command_path) {
$command = $command_path;
} else {
# Try to extract a module name from the command
# Get list of directories
opendir (my $DIR, $root);
my @module_dirs = grep { -d "$root/$_" } readdir($DIR);
# See if any of them are a substring of $subcmd
for my $dir (@module_dirs) {
if (index($subcmd, $dir) == 0) {
$module_name = $dir;
my $barecmd = substr($subcmd, -(length($subcmd)-length($module_name)-1));
$command = File::Spec->catfile($root, $dir, 'bin', $barecmd);
# Could be .pl or no extension
if ( -x $command ) {
last;
} elsif ( -x $command . ".pl" ) {
$command = $command . ".pl";
last;
}
}
}
}
if ($optref->{'commands'} &&
$optref->{'commands'} =~ /^(stats|status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
exit system("$0 server $optref->{'commands'}");
} elsif ($command) {
return $command;
} else {
die RED, "Unrecognized subcommand: $subcmd", RESET , "\n";
}
# Check for a root-level command (in "$root/bin")
my $command_path;
if ($subcmd) {
$command_path = File::Spec->catfile($root, 'bin', $subcmd);
}
else {
$command_path = File::Spec->catfile($root, 'bin', 'webmin');
}
my $module_name;
my $command;
if ( -x $command_path) {
$command = $command_path;
}
else {
# Try to extract a module name from the command
# Get list of directories
opendir (my $DIR, $root);
my @module_dirs = grep { -d "$root/$_" } readdir($DIR);
closedir($DIR);
# See if any of them are a substring of $subcmd
for my $dir (@module_dirs) {
if (index($subcmd, $dir) == 0) {
$module_name = $dir;
my $barecmd = substr($subcmd, -(length($subcmd)-length($module_name)-1));
$command = File::Spec->catfile($root, $dir, 'bin', $barecmd);
# Could be .pl or no extension
if ( -x $command ) {
last;
}
elsif ( -x $command . ".pl" ) {
$command = $command . ".pl";
last;
}
}
}
}
if ($optref->{'commands'} &&
$optref->{'commands'} =~ /^(stats|status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
exit system("$0 server $optref->{'commands'}");
}
elsif ($command) {
return $command;
}
else {
die RED, "Unrecognized subcommand: $subcmd", RESET , "\n";
}
}
sub list_commands {
my ($optref) = @_;
sub list_commands
{
my ($optref) = @_;
my $root = root($optref->{'config'});
my @commands;
my $root = root($optref->{'config'});
my @commands;
# Find and list global commands
for my $command (glob ("$root/bin/*")) {
my ($bin, $path) = fileparse($command);
if ($bin =~ "webmin") {
next;
}
if ($optref->{'describe'}) {
# Display name and description
say YELLOW, "$bin", RESET;
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
} else {
if (wantarray) {
push(@commands, $bin);
} else {
# Just list the names
say "$bin";
}
}
}
# Find and list global commands
for my $command (glob ("$root/bin/*")) {
my ($bin, $path) = fileparse($command);
if ($bin =~ "webmin") {
next;
}
if ($optref->{'describe'}) {
# Display name and description
say YELLOW, "$bin", RESET;
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
}
else {
if (wantarray) {
push(@commands, $bin);
}
else {
# Just list the names
say "$bin";
}
}
}
my @modules;
# Find all module directories with something in bin
for my $command (glob ("$root/*/bin/*")) {
my ($bin, $path) = fileparse($command);
my $module = (split /\//, $path)[-2];
if ($optref->{'describe'}) {
# Display name and description
say YELLOW, "$module-$bin", RESET;
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
} else {
if (wantarray) {
push(@modules, "$module-$bin");
} else {
# Just list the names
say "$module-$bin";
}
}
}
my @modules;
# Find all module directories with something in bin
for my $command (glob ("$root/*/bin/*")) {
my ($bin, $path) = fileparse($command);
my $module = (split /\//, $path)[-2];
if ($optref->{'describe'}) {
# Display name and description
say YELLOW, "$module-$bin", RESET;
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
}
else {
if (wantarray) {
push(@modules, "$module-$bin");
}
else {
# Just list the names
say "$module-$bin";
}
}
}
if (wantarray) {
return (@commands, @modules);
}
if (wantarray) {
return (@commands, @modules);
}
}
# Display either a short usage message (--help) or a full manual (--man)
sub man_command {
my ($optref, $subcmd) = @_;
sub man_command
{
my ($optref, $subcmd) = @_;
my $root = root($optref->{'config'});
my $command_path = get_command_path($root, $subcmd, $optref);
my $root = root($optref->{'config'});
my $command_path = get_command_path($root, $subcmd, $optref);
$ENV{'PAGER'} ||= "more";
open(my $PAGER, "|-", "$ENV{'PAGER'}");
if ($optref->{'help'}) {
pod2usage( -input => $command_path );
} else {
pod2usage( -verbose => 99,
-input => $command_path,
-output => $PAGER);
}
$ENV{'PAGER'} ||= "more";
open(my $PAGER, "|-", "$ENV{'PAGER'}");
if ($optref->{'help'}) {
pod2usage( -input => $command_path );
}
else {
pod2usage( -verbose => 99,
-input => $command_path,
-output => $PAGER);
}
close($PAGER);
}
sub root {
my ($config) = @_;
open(my $CONF, "<", "$config/miniserv.conf") || die RED,
"Failed to open $config/miniserv.conf", RESET , "\n";
my $root;
while (<$CONF>) {
if (/^root=(.*)/) {
$root = $1;
}
}
close($CONF);
# Does the Webmin root exist?
if ( $root ) {
die "$root is not a directory. Is --config correct?\n" unless (-d $root);
} else {
die "Unable to determine Webmin installation directory from $ENV{'WEBMIN_CONFIG'}\n";
}
sub root
{
my ($config) = @_;
open(my $CONF, "<", "$config/miniserv.conf") ||
die RED, "Failed to open $config/miniserv.conf", RESET , "\n";
my $root;
while (<$CONF>) {
if (/^root=(.*)/) {
$root = $1;
}
}
close($CONF);
return $root;
# Does the Webmin root exist?
if ( $root ) {
die "$root is not a directory. Is --config correct?\n" unless (-d $root);
}
else {
die "Unable to determine Webmin installation directory ".
"from $ENV{'WEBMIN_CONFIG'}\n";
}
return $root;
}
# loadlibs - Load libraries from the Webmin vendor dir
# as those may not be installed as dependency, because
# Webmin already provides them from package manager
# perspective.
sub loadlibs {
my ($optref) = @_;
$optref->{'config'} ||= "/etc/webmin";
my $root = root($optref->{'config'});
my $libroot = "$root/vendor_perl";
eval "use lib '$libroot'";
eval "use File::Basename";
eval "use File::Spec";
sub loadlibs
{
my ($optref) = @_;
$optref->{'config'} ||= "/etc/webmin";
my $root = root($optref->{'config'});
my $libroot = "$root/vendor_perl";
eval "use lib '$libroot'";
eval "use File::Basename";
eval "use File::Spec";
}
1;
@@ -462,7 +489,7 @@ Returns Webmin and other modules and themes versions installed (only those for w
=head1 LICENSE AND COPYRIGHT
Copyright 2018 Jamie Cameron <jcameron@webmin.com>
Joe Cooper <joe@virtualmin.com>
Ilia Ross <ilia@virtualmin.com>
Copyright 2018 Jamie Cameron <jamie@webmin.com>
Joe Cooper <joe@virtualmin.com>
Ilia Ross <ilia@virtualmin.com>

View File

@@ -70,12 +70,18 @@ sub get_rand_flag
if ($gconfig{'os_type'} =~ /-linux$/ &&
$config{'force_random'} eq '0' &&
-r "/dev/urandom" &&
&compare_version_numbers($bind_version, 9) >= 0 &&
&compare_version_numbers($bind_version, '9.14.2') < 0) {
# Version: 9.14.2 deprecated the use of -r option
# in favor of using /dev/random [bugs:#5370]
return "-r /dev/urandom";
&compare_version_numbers($bind_version, 9) >= 0) {
if (&compare_version_numbers($bind_version, '9.14.2') < 0) {
return "-r /dev/urandom";
}
else {
# Version: 9.14.2 deprecated the use of -r option
# in favor of using /dev/random [bugs:#5370]. So no
# entropy generation is needed.
return undef;
}
}
# No random flag, and entropy is needed
return "";
}
@@ -1118,7 +1124,7 @@ else {
@v = ( );
}
if ($type eq "A" || $type eq "AAAA") {
print &ui_table_row($text{'value_A1'},
print &ui_table_row($text{"value_${type}1"},
&ui_textbox("value0", $v[0], 20)." ".
(!defined($_[5]) && $type eq "A" ?
&free_address_button("value0") : ""), 3);
@@ -3458,7 +3464,8 @@ closedir(ZONEDIR);
# Fork a background job to do lots of IO, to generate entropy
my $pid;
if (!&get_rand_flag()) {
my $flag = &get_rand_flag();
if (defined($flag) && !$flag) {
$pid = fork();
if (!$pid) {
exec("find / -type f >/dev/null 2>&1");
@@ -3507,7 +3514,7 @@ else {
"cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($zonesize).
" -n ZONE ".&get_rand_flag()." $dom 2>&1");
" -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
if ($?) {
kill('KILL', $pid) if ($pid);
return $out;
@@ -3519,7 +3526,8 @@ else {
"cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($size).
" -n ZONE -f KSK ".&get_rand_flag()." $dom 2>&1");
" -n ZONE -f KSK ".($flag || "")." ".
quotemeta($dom)." 2>&1");
kill('KILL', $pid) if ($pid);
if ($?) {
return $out;
@@ -3589,7 +3597,8 @@ $zonekey || return "Could not find DNSSEC zone key";
# Fork a background job to do lots of IO, to generate entropy
my $pid;
if (!&get_rand_flag()) {
my $flag = &get_rand_flag();
if (defined($flag) && !$flag) {
$pid = fork();
if (!$pid) {
exec("find / -type f >/dev/null 2>&1");
@@ -3606,7 +3615,7 @@ my $alg = $zonekey->{'algorithm'};
my $out = &backquote_logged(
"cd ".quotemeta($dir)." && ".
"$config{'keygen'} -a ".quotemeta($alg)." -b ".quotemeta($zonesize).
" -n ZONE ".&get_rand_flag()." $dom 2>&1");
" -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
kill('KILL', $pid) if ($pid);
if ($?) {
return "Failed to generate new zone key : $out";

View File

@@ -44,7 +44,9 @@ if (&is_raw_format_records($file)) {
my $temp = &transname();
&copy_source_dest($file, $temp);
my $out = &backquote_logged("named-compilezone -f raw -F text ".
"-o $file $zone->{'name'} $temp 2>&1");
"-o ".quotemeta($file)." ".
quotemeta($zone->{'name'})." ".
quotemeta($temp)." 2>&1");
&error(&text('convert_ecompile', "<tt>".&html_escape($out)."</tt>"))
if ($?);
&unlink_file($temp);

View File

@@ -127,7 +127,7 @@ if (&have_dnssec_tools_support()) {
print "<br>\n<br>\n";
if ((($lsdnssec=dt_cmdpath('lsdnssec')) ne '')) {
my $cmd = "$lsdnssec -z $dom $rrfile";
my $cmd = "$lsdnssec -z ".quotemeta($dom)." $rrfile";
my $out = &backquote_command("$cmd");
print &ui_textarea("lsdnssec", $out, 12, 80, "soft", 0,
"readonly style='width:90%'");

View File

@@ -35,10 +35,13 @@ my $rootfile = $_[4] ? $file : &make_chroot($file);
my $FILE;
if (&is_raw_format_records($rootfile)) {
# Convert from raw format first
&has_command("named-compilezone") ||
my $compilezone = &has_command("named-compilezone");
$compilezone ||
&error("Zone file $rootfile is in raw format, but the ".
"named-compilezone command is not installed");
open($FILE, "named-compilezone -f raw -F text -o - $origin $rootfile |");
open($FILE, "-|", $compilezone,
"-f", "raw", "-F", "text", "-o", "-",
$origin, $rootfile);
}
else {
# Can read text format records directly

View File

@@ -1,5 +1,6 @@
#!/usr/local/bin/perl
# Add or update a server or group from the webmin servers module
use strict;
use warnings;
no warnings 'redefine';
@@ -94,12 +95,17 @@ foreach my $s (@add) {
next;
}
if (!$in{'name_def'} && &check_ipaddress($in{'name'})) {
print &text('add_eipaddr', $s->{'host'}),"<p>\n";
print &text('add_eipaddr', $s->{'host'}),"<p>\n";
next;
}
my @rzones = grep { $_->{'type'} ne 'view' }
&remote_foreign_call($s, "bind8", "list_zone_names");
my @zn = &remote_foreign_call($s, "bind8", "list_zone_names");
if ($add_error_msg) {
print "$add_error_msg<p>\n";
next;
}
my @rzones = grep { $_->{'type'} ne 'view' } @zn;
print &text('add_ok', $s->{'host'}, scalar(@rzones)),"<p>\n";
$s->{'sec'} = $in{'sec'};
$s->{'nsname'} = $in{'name_def'} ? undef : $in{'name'};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
#!/usr/local/bin/perl
# Change the label of a slice (GPT label or glabel)
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
my $current_label = get_device_label_name(
disk => $disk,
slice => $slice,
disk_structure => $disk_structure
);
my $suggested_label = $slice->{'device'};
$suggested_label =~ s{^/dev/}{};
&ui_print_header( $slice->{'desc'}, $text{'slice_label_title'}, "" );
print &ui_form_start( "save_slice_label.cgi", "post" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_table_start( $text{'slice_label_header'}, undef, 2 );
print &ui_table_row( $text{'part_device'},
"<tt>" . html_escape( $slice->{'device'} ) . "</tt>" );
print &ui_table_row( $text{'slice_label_current'},
$current_label ? "<tt>" . html_escape($current_label) . "</tt>" : "-" );
print &ui_table_row( $text{'slice_label_new'},
&ui_textbox( "label", $suggested_label, 20 ) );
print &ui_table_end();
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -6,47 +6,63 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'npart_err'});
&error_setup( $text{'npart_err'} );
# Get the disk
my @disks = &list_disks_partitions();
# Validate input parameters
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
# Validate inputs, starting with slice number
my $part = { };
$in{'letter'} =~ /^[a-d]$/i || &error($text{'npart_eletter'});
$in{'letter'} = lc($in{'letter'});
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{$slice->{'parts'}};
$clash && &error(&text('npart_eclash', $in{'letter'}));
my $part = {};
$in{'letter'} =~ /^[a-h]$/i || &error( $text{'npart_eletter'} );
$in{'letter'} = lc( $in{'letter'} );
# Partition 'c' is reserved in BSD disklabels (represents the whole slice)
$in{'letter'} ne 'c' || &error( $text{'npart_ereserved'} );
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{ $slice->{'parts'} };
$clash && &error( &text( 'npart_eclash', $in{'letter'} ) );
$part->{'letter'} = $in{'letter'};
# Start and end blocks
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
$in{'start'} < $in{'end'} || &error($text{'npart_erange'});
$in{'start'} =~ /^\d+$/ || &error( $text{'nslice_estart'} );
$in{'end'} =~ /^\d+$/ || &error( $text{'nslice_eend'} );
$in{'start'} < $in{'end'} || &error( $text{'npart_erange'} );
$part->{'startblock'} = $in{'start'};
$part->{'blocks'} = $in{'end'} - $in{'start'};
$part->{'blocks'} = $in{'end'} - $in{'start'} + 1;
# Slice type
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
or &error( $text{'npart_etype'} || 'Invalid partition type' );
$part->{'type'} = $in{'type'};
# Do the creation
&ui_print_header($slice->{'desc'}, $text{'npart_title'}, "");
&ui_print_header( $slice->{'desc'}, $text{'npart_title'}, "" );
print &text('npart_creating', $in{'letter'}, $slice->{'desc'}),"<p>\n";
my $err = &save_partition($disk, $slice, $part);
print &text( 'npart_creating', $in{'letter'},
&html_escape( $slice->{'desc'} ) ), "<p>\n";
# Actually create the partition inside the slice (initialize BSD label if needed)
my $err = &create_partition( $disk, $slice, $part );
if ($err) {
print &text('npart_failed', $err),"<p>\n";
}
print &text( 'npart_failed', &html_escape($err) ), "<p>\n";
}
else {
print &text('npart_done'),"<p>\n";
&webmin_log("create", "part", $part->{'device'}, $part);
}
print &text('npart_done'), "<p>\n";
&webmin_log( "create", "part", $part->{'device'}, $part );
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -1,65 +1,147 @@
#!/usr/local/bin/perl
# Actually create a new slice
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
&error_setup($text{'nslice_err'});
our ( %in, %text, $module_name );
ReadParse();
error_setup( $text{'nslice_err'} );
# Get the disk
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
# Get the disk using first() for an early exit on match
my @disks = list_disks_partitions();
# Validate input parameters
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} || 'Invalid device' );
my $disk;
foreach my $d (@disks) {
if ( $d->{'device'} eq $in{'device'} ) {
$disk = $d;
last;
}
}
# Validate device parameter to prevent path traversal and command injection
$disk or error( $text{'disk_egone'} );
# Prefer GPART total blocks for bounds
( my $base_dev = $in{'device'} ) =~ s{^/dev/}{};
my $ds = get_disk_structure($base_dev);
my $disk_blocks =
( $ds && $ds->{'total_blocks'} )
? $ds->{'total_blocks'}
: ( $disk->{'blocks'} || 0 );
# Validate inputs, starting with slice number
my $slice = { };
$in{'number'} =~ /^\d+$/ || &error($text{'nslice_enumber'});
my ($clash) = grep { $_->{'number'} == $in{'number'} } @{$disk->{'slices'}};
$clash && &error(&text('nslice_eclash', $in{'number'}));
my $slice = {};
$in{'number'} =~ /^\d+$/ or error( $text{'nslice_enumber'} );
# Check for clash using first() with a loop exiting on first match
my $clash;
foreach my $s ( @{ $disk->{'slices'} } ) {
if ( $s->{'number'} == $in{'number'} ) {
$clash = $s;
last;
}
}
$slice->{'number'} = $in{'number'};
# Start and end blocks
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
$in{'start'} < $in{'end'} || &error($text{'nslice_erange'});
$in{'start'} =~ /^\d+$/ or error( $text{'nslice_estart'} );
$in{'end'} =~ /^\d+$/ or error( $text{'nslice_eend'} );
( $in{'start'} < $in{'end'} ) or error( $text{'nslice_erange'} );
# total_blocks is the block *after* the last valid block, so end must be < total_blocks
( $in{'end'} < $disk_blocks )
or error( text( 'nslice_emax', $disk_blocks - 1 ) );
# Ensure the new slice does not overlap existing slices
foreach my $s ( @{ $disk->{'slices'} } ) {
my $s_start = $s->{'startblock'};
my $s_end = $s->{'startblock'} + $s->{'blocks'} - 1;
if ( !( $in{'end'} < $s_start || $in{'start'} > $s_end ) ) {
error( "Requested slice range overlaps with existing slice #"
. $s->{'number'} );
}
}
$slice->{'startblock'} = $in{'start'};
$slice->{'blocks'} = $in{'end'} - $in{'start'};
$slice->{'blocks'} = $in{'end'} - $in{'start'} + 1;
# Slice type
$in{'type'} =~ /^[a-zA-Z0-9_-]+$/ or error( $text{'nslice_etype'} );
length( $in{'type'} ) <= 20 or error( $text{'nslice_etype'} );
$slice->{'type'} = $in{'type'};
# Do the creation
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
print &text('nslice_creating', $in{'number'}, $disk->{'desc'}),"<p>\n";
my $err = &create_slice($disk, $slice);
ui_print_header( $disk->{'desc'}, $text{'nslice_title'}, "" );
print text( 'nslice_creating', $in{'number'}, &html_escape( $disk->{'desc'} ) ),
"<p>\n";
my $err = create_slice( $disk, $slice );
if ($err) {
print &text('nslice_failed', $err),"<p>\n";
}
print text( 'nslice_failed', &html_escape($err) ), "<p>\n";
}
else {
print &text('nslice_done'),"<p>\n";
}
print text('nslice_done'), "<p>\n";
if (!$err && $in{'makepart'}) {
# Also create a partition
print &text('nslice_parting', $in{'number'}, $disk->{'desc'}),"<p>\n";
my $err = &initialize_slice($disk, $slice);
if ($err) {
print &text('nslice_pfailed', $err),"<p>\n";
}
else {
print &text('nslice_pdone'),"<p>\n";
}
}
# Auto-label the new partition provider with its name if scheme is GPT or BSD
my $base = $disk->{'device'};
$base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ( $ds && $ds->{'scheme'} ) {
if (!$err) {
&webmin_log("create", "slice", $slice->{'device'}, $slice);
}
# Determine provider and label text
my $label_text = slice_name($slice); # e.g., da8s2 or da0p2
if ( $ds->{'scheme'} =~ /GPT/i ) {
my $idx = $slice->{'number'};
if ($idx) {
my $cmd2 =
"gpart modify -i $idx -l "
. quote_path($label_text) . " "
. quote_path($base);
my $out2 = backquote_command("$cmd2 2>&1");
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# If it fails, ignore silently
}
}
else {
# On MBR, if BSD label exists we can set label once created; ignore for now
}
}
}
if ( !$err && $in{'makepart'} ) {
# Also create a partition (initialize slice label)
my $part_err = initialize_slice( $disk, $slice );
if ($part_err) {
print text( 'nslice_pfailed', &html_escape($part_err) ), "<p>\n";
}
else {
print text('nslice_pdone'), "<p>\n";
}
}
if ( !$err ) {
# Auto-label GPT partitions with their device name (e.g., da8p2)
my $base = $disk->{'device'};
$base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
my $slice_devname = $slice->{'device'};
$slice_devname =~ s{^/dev/}{}; # e.g., da8p2
my $idx = $slice->{'number'};
if ( $idx && $slice_devname ) {
my $label_cmd =
"gpart modify -i $idx -l "
. quote_path($slice_devname) . " "
. quote_path($base) . " 2>&1";
my $label_out = backquote_command($label_cmd);
# Ignore errors - labeling is optional
}
}
webmin_log( "create", "slice", $slice->{'device'}, $slice );
}
my $url_device = &urlize( $in{'device'} );
ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

View File

@@ -6,47 +6,62 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
# Validate inputs
($in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ && $in{'device'} !~ /\.\./)
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$disk || &error( $text{'disk_egone'} );
my $in_slice_num = int($in{'slice'});
my ($slice) = grep { int($_->{'number'}) == $in_slice_num } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
&ui_print_header($part->{'desc'}, $text{'dpart_title'}, "");
&ui_print_header( $part->{'desc'}, $text{'dpart_title'}, "" );
if ($in{'confirm'}) {
# Delete it
print &text('dpart_deleting', $part->{'desc'}),"<p>\n";
my $err = &delete_partition($disk, $slice, $part);
if ($err) {
print &text('dpart_failed', $err),"<p>\n";
}
else {
print $text{'dpart_done'},"<p>\n";
&webmin_log("delete", "part", $part->{'device'}, $part);
}
}
if ( $in{'confirm'} ) {
# Delete it
print &text( 'dpart_deleting', &html_escape( $part->{'desc'} ) ), "<p>\n";
my $err = &delete_partition( $disk, $slice, $part );
if ($err) {
print &text( 'dpart_failed', &html_escape($err) ), "<p>\n";
}
else {
print $text{'dpart_done'}, "<p>\n";
&webmin_log( "delete", "part", $part->{'device'}, $part );
}
}
else {
# Ask first
my @st = &fdisk::device_status($part->{'device'});
my $use = &fdisk::device_status_link(@st);
print &ui_confirmation_form(
"delete_part.cgi",
&text('dpart_rusure', "<tt>$part->{'device'}</tt>"),
[ [ "device", $in{'device'} ],
[ "slice", $in{'slice'} ],
[ "part", $in{'part'} ] ],
[ [ "confirm", $text{'dslice_confirm'} ] ],
undef,
$use ? &text('dpart_warn', $use) : undef);
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
# Ask first
my @st = &fdisk::device_status( $part->{'device'} );
my $use = &fdisk::device_status_link(@st); # returns safe HTML link(s); ensure upstream sanitization
print &ui_confirmation_form(
"delete_part.cgi",
&text(
'dpart_rusure',
"<tt>" . &html_escape( $part->{'device'} ) . "</tt>"
),
[
[ "device", $in{'device'} ],
[ "slice", $in{'slice'} ],
[ "part", $in{'part'} ]
],
# Use partition-specific confirmation text key if available
[ [ "confirm", $text{'dpart_confirm'} || $text{'dslice_confirm'} ] ],
undef,
$use ? &text( 'dpart_warn', $use ) : undef
);
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -6,53 +6,62 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
&ui_print_header($slice->{'desc'}, $text{'dslice_title'}, "");
&ui_print_header( $slice->{'desc'}, $text{'dslice_title'}, "" );
if ($in{'confirm'}) {
# Delete it
print &text('dslice_deleting', $slice->{'desc'}),"<p>\n";
my $err = &delete_slice($disk, $slice);
if ($err) {
print &text('dslice_failed', $err),"<p>\n";
}
else {
print $text{'dslice_done'},"<p>\n";
&webmin_log("delete", "slice", $slice->{'device'}, $slice);
}
}
if ( $in{'confirm'} ) {
# Delete it
print &text( 'dslice_deleting', &html_escape( $slice->{'desc'} ) ), "<p>\n";
my $err = &delete_slice( $disk, $slice );
if ($err) {
print &text( 'dslice_failed', &html_escape($err) ), "<p>\n";
}
else {
print $text{'dslice_done'}, "<p>\n";
&webmin_log( "delete", "slice", $slice->{'device'}, $slice );
}
}
else {
# Ask first
my @warn;
my @st = &fdisk::device_status($slice->{'device'});
if (@st) {
push(@warn, &fdisk::device_status_link(@st));
}
foreach my $p (@{$slice->{'parts'}}) {
my @st = &fdisk::device_status($p->{'device'});
if (@st) {
push(@warn, &fdisk::device_status_link(@st));
}
}
print &ui_confirmation_form(
"delete_slice.cgi",
&text('dslice_rusure', "<tt>$slice->{'device'}</tt>"),
[ [ "device", $in{'device'} ],
[ "slice", $in{'slice'} ] ],
[ [ "confirm", $text{'dslice_confirm'} ] ],
undef,
@warn ? &text('dslice_warn', join(" ", @warn)) : undef);
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# Ask first
my @warn;
my @st = &fdisk::device_status( $slice->{'device'} );
if (@st) {
push( @warn, &fdisk::device_status_link(@st) );
}
foreach my $p ( @{ $slice->{'parts'} } ) {
my @st = &fdisk::device_status( $p->{'device'} );
if (@st) {
push( @warn, &fdisk::device_status_link(@st) );
}
}
print &ui_confirmation_form(
"delete_slice.cgi",
&text(
'dslice_rusure',
"<tt>" . &html_escape( $slice->{'device'} ) . "</tt>"
),
[ [ "device", $in{'device'} ], [ "slice", $in{'slice'} ] ],
[ [ "confirm", $text{'dslice_confirm'} ] ],
undef,
@warn
? &text( 'dslice_warn', join( " ", @warn ) )
: undef
);
}
my $url_device = &urlize( $in{'device'} );
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

View File

@@ -1,100 +1,462 @@
#!/usr/local/bin/perl
# Show details of a disk, and slices on it
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
my $extwidth = 300;
my $extwidth = 100;
# Get the disk
my @disks = &list_disks_partitions();
# Validate input parameters
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error( $text{'disk_edevice'} );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
$disk || &error( $text{'disk_egone'} );
&ui_print_header($disk->{'desc'}, $text{'disk_title'}, "");
# Cache commonly used values
my $device = $disk->{'device'};
my $device_url = &urlize($device);
my $desc = $disk->{'desc'};
# Show disk details
my @info = ( );
push(@info, &text('disk_dsize', &nice_size($disk->{'size'})));
if ($disk->{'model'}) {
push(@info, &text('disk_model', $disk->{'model'}));
# Prefer total blocks from gpart header when available
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = &get_disk_structure($base_device);
my $disk_blocks =
( $disk_structure && $disk_structure->{'total_blocks'} )
? $disk_structure->{'total_blocks'}
: ( $disk->{'blocks'} || 1000000 );
# Precompute a scale factor for extent image widths
my $scale = $extwidth / ( $disk_blocks || 1 );
&ui_print_header( $disk->{'desc'}, $text{'disk_title'}, "" );
# Debug toggle bar
print
"<div class='debug-toggle' style='margin-bottom: 15px; text-align: right;'>";
if ( $in{'debug'} ) {
print
"<a href='edit_disk.cgi?device=$device_url' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_hide_debug'}</a>";
}
else {
print
"<a href='edit_disk.cgi?device=$device_url&debug=1' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_show_debug'}</a>";
}
print "</div>";
# Get detailed disk information from geom and disk structure from gpart show (cache disk_structure entries)
my $geom_info = &get_detailed_disk_info($device);
my $entries = $disk_structure
&& $disk_structure->{'entries'} ? $disk_structure->{'entries'} : [];
print &ui_table_start( $text{'disk_details'}, "width=100%", 2 );
# Prefer mediasize (bytes) for accurate size; fallback to stat-based size
my $disk_bytes =
( $disk_structure && $disk_structure->{'mediasize'} )
? $disk_structure->{'mediasize'}
: $disk->{'size'};
print &ui_table_row( $text{'disk_dsize'}, &safe_nice_size($disk_bytes) );
if ( $disk->{'model'} ) {
print &ui_table_row( $text{'disk_model'},
&html_escape( $disk->{'model'} ) );
}
print &ui_table_row( $text{'disk_device'},
"<tt>" . &html_escape( $disk->{'device'} ) . "</tt>" );
# Get disk scheme
print &ui_table_row( $text{'disk_scheme'},
$disk_structure
? &html_escape( $disk_structure->{'scheme'} )
: $text{'disk_unknown'} );
# GEOM details
if ($geom_info) {
print &ui_table_hr();
print &ui_table_row( $text{'disk_geom_header'},
"<b>$text{'disk_geom_details'}</b>", 2 );
if ( $geom_info->{'mediasize'} ) {
print &ui_table_row( $text{'disk_mediasize'},
$geom_info->{'mediasize'} );
}
if ( $geom_info->{'sectorsize'} ) {
print &ui_table_row( $text{'disk_sectorsize'},
$geom_info->{'sectorsize'} . " " . $text{'disk_bytes'} );
}
if ( $geom_info->{'stripesize'} ) {
print &ui_table_row( $text{'disk_stripesize'},
$geom_info->{'stripesize'} . " " . $text{'disk_bytes'} );
}
if ( $geom_info->{'stripeoffset'} ) {
print &ui_table_row( $text{'disk_stripeoffset'},
$geom_info->{'stripeoffset'} . " " . $text{'disk_bytes'} );
}
if ( $geom_info->{'mode'} ) {
print &ui_table_row( $text{'disk_mode'},
&html_escape( $geom_info->{'mode'} ) );
}
if ( $geom_info->{'rotationrate'} ) {
if ( $geom_info->{'rotationrate'} eq "0" ) {
print &ui_table_row( $text{'disk_rotationrate'},
$text{'disk_ssd'} );
}
push(@info, &text('disk_cylinders', $disk->{'cylinders'}));
push(@info, &text('disk_blocks', $disk->{'blocks'}));
push(@info, &text('disk_device', "<tt>$disk->{'device'}</tt>"));
print &ui_links_row(\@info),"<p>\n";
else {
print &ui_table_row( $text{'disk_rotationrate'},
$geom_info->{'rotationrate'} . " " . $text{'disk_rpm'} );
}
}
if ( $geom_info->{'ident'} ) {
print &ui_table_row( $text{'disk_ident'},
&html_escape( $geom_info->{'ident'} ) );
}
if ( $geom_info->{'lunid'} ) {
print &ui_table_row( $text{'disk_lunid'},
&html_escape( $geom_info->{'lunid'} ) );
}
if ( $geom_info->{'descr'} ) {
print &ui_table_row( $text{'disk_descr'},
&html_escape( $geom_info->{'descr'} ) );
}
}
# Advanced information (cylinders, blocks)
print &ui_table_hr();
print &ui_table_row( $text{'disk_advanced_header'},
"<b>$text{'disk_advanced_details'}</b>", 2 );
if ( $disk->{'cylinders'} ) {
print &ui_table_row( $text{'disk_cylinders'}, $disk->{'cylinders'} );
}
print &ui_table_row( $text{'disk_blocks'}, $disk->{'blocks'} );
print &ui_table_end();
# Debug: print raw outputs if debug mode is enabled
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
# Debug: gpart show output
my $cmd = "gpart show -l " . &quote_path($base_device) . " 2>&1";
my $out = &backquote_command($cmd);
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_gpart'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Command: "
. &html_escape($cmd)
. "\nOutput:\n"
. &html_escape($out)
. "\n</pre>";
print "</div></div>";
# Debug: disk structure
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_structure'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Disk Structure:\n";
foreach my $key ( sort keys %$disk_structure ) {
if ( $key eq 'entries' ) {
print "entries: [\n";
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
print " {\n";
foreach my $k ( sort keys %$entry ) {
print " $k: " . &html_escape( $entry->{$k} ) . "\n";
}
print " },\n";
}
print "]\n";
}
else {
print "$key: " . &html_escape( $disk_structure->{$key} ) . "\n";
}
}
print "</pre>";
print "</div></div>";
# Debug: Raw GEOM output
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_geom'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Raw GEOM output:\n";
print &html_escape(
&backquote_command(
"geom disk list " . &quote_path($device) . " 2>/dev/null"
)
);
print "</pre>";
print "</div></div>";
print "</div>";
}
# Build partition details from disk_structure (no separate gpart list call)
my %part_details = ();
if ( $disk_structure && $disk_structure->{'partitions'} ) {
%part_details = %{ $disk_structure->{'partitions'} };
}
# Ensure we have names/labels for any entries missing from partitions map
if ( $disk_structure && $disk_structure->{'entries'} ) {
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
next unless ( $entry->{'type'} eq 'partition' && $entry->{'index'} );
my $part_num = $entry->{'index'};
$part_details{$part_num} ||= {};
$part_details{$part_num}->{'name'} ||=
$base_device
. ( ( $disk_structure->{'scheme'} eq 'GPT' )
? "p$part_num"
: "s$part_num" );
if ( $entry->{'label'} && $entry->{'label'} ne '(null)' ) {
$part_details{$part_num}->{'label'} ||= $entry->{'label'};
}
$part_details{$part_num}->{'type'} ||=
$entry->{'part_type'} || 'unknown';
}
}
# Build ZFS devices cache
my ( $zfs_pools, $zfs_devices ) = &build_zfs_devices_cache();
# Debug ZFS pools if debug mode is enabled
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>";
my $cmd = "zpool status 2>&1";
my $out = &backquote_command($cmd);
print "Command: "
. &html_escape($cmd)
. "\nOutput:\n"
. &html_escape($out) . "\n";
print "</pre>";
print "</div></div>";
print "</div>";
}
# Debug: Print partition details mapping if debug enabled
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_part_details'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Partition Details Mapping:\n";
foreach my $pnum ( sort { $a <=> $b } keys %part_details ) {
print " $pnum: {\n";
foreach my $k ( sort keys %{ $part_details{$pnum} } ) {
print " $k: "
. &html_escape( $part_details{$pnum}->{$k} ) . "\n";
}
print " },\n";
}
print "</pre>";
print "</div></div>";
print "</div>";
}
# Get sector size
my $sectorsize =
$disk_structure->{'sectorsize'} || &get_disk_sectorsize($device) || 512;
my $sectorsize_text = $sectorsize ? "$sectorsize" : "512";
# Show partitions table
my @links = ( "<a href='slice_form.cgi?device=".&urlize($disk->{'device'}).
"&new=1'>".$text{'disk_add'}."</a>" );
if (@{$disk->{'slices'}}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'disk_no'},
$text{'disk_type'},
$text{'disk_extent'},
$text{'disk_size'},
$text{'disk_start'},
$text{'disk_end'},
$text{'disk_use'},
]);
foreach my $p (@{$disk->{'slices'}}) {
# Create images for the extent
my $ext = "";
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($p->{'startblock'} - 1) /
$disk->{'blocks'};
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
$p->{'extended'} ? "ext" : "use",
$extwidth*($p->{'blocks'}) /
$disk->{'blocks'};
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($disk->{'blocks'} - $p->{'startblock'} -
$p->{'blocks'}) / $disk->{'blocks'};
my @links =
( "<a href='slice_form.cgi?device=$device_url&amp;new=1'>"
. $text{'disk_add'}
. "</a>" );
if (@$entries) {
print &ui_links_row( \@links );
print &ui_columns_start(
[
$text{'disk_no'}, # Row number
$text{'disk_partno'}, # Part. No.
$text{'disk_partname'}, # Part. Name
$text{'disk_partlabel'}, # Part. Label
$text{'disk_subpart'}, # Sub-part.
$text{'disk_extent'}, # Extent
$text{'disk_start'}, # Startblock
$text{'disk_end'}, # Endblock
$text{'disk_size'}, # Size
$text{'disk_format'}, # Format type
$text{'disk_use'}, # Used by
$text{'disk_role'}, # Role Type
]
);
my $row_number = 1;
foreach my $entry (@$entries) {
my @cols = ();
push( @cols, $row_number++ );
if ( $entry->{'type'} eq 'free' ) {
my $start = $entry->{'start'};
my $end = $entry->{'start'} + $entry->{'size'} - 1;
my $create_url =
"slice_form.cgi?device=$device_url&new=1&start=$start&end=$end";
push( @cols,
"<a href='$create_url' style='color: green;'>"
. $text{'disk_free'}
. "</a>" );
push( @cols, "-" );
push( @cols, "-" );
push( @cols, "-" );
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $entry->{'start'} - 1 );
$ext .=
sprintf
"<img src='images/gap.gif' height='10' width='%d' style='background-color: #8f8;'>",
$scale * ( $entry->{'size'} );
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $disk_blocks - $entry->{'start'} - $entry->{'size'} );
push( @cols, $ext );
push( @cols, $start );
push( @cols, $end );
push( @cols, $entry->{'size_human'} );
push( @cols, $text{'disk_free_space'} );
push( @cols, $text{'disk_available'} );
push( @cols, "-" );
}
else {
my $part_num = $entry->{'index'};
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $entry->{'start'} - 1 );
$ext .= sprintf "<img src='images/use.gif' height='10' width='%d'>",
$scale * ( $entry->{'size'} );
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $disk_blocks - $entry->{'start'} - $entry->{'size'} );
my $url = "edit_slice.cgi?device=$device_url&amp;slice="
. &urlize($part_num);
push( @cols, "<a href='$url'>" . &html_escape($part_num) . "</a>" );
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
my $n = scalar(@{$p->{'parts'}});
my $part_info = $part_details{$part_num};
my $part_name = $part_info ? $part_info->{'name'} : "-";
push( @cols, &html_escape($part_name) );
my $part_label =
$part_info
? $part_info->{'label'}
: ( $entry->{'label'} eq "(null)" ? "-" : $entry->{'label'} );
push( @cols, &html_escape($part_label) );
# Add row for the slice
my $url = "edit_slice.cgi?device=".&urlize($disk->{'device'}).
"&slice=".$p->{'number'};
my $nlink = "<a href='$url'>$p->{'number'}</a>";
$nlink = "<b>$nlink</b>" if ($p->{'active'});
print &ui_columns_row([
$nlink,
"<a href='$url'>".&fdisk::tag_name($p->{'type'})."</a>",
$ext,
&nice_size($p->{'size'}),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$use ? $use :
$n ? &text('disk_scount', $n) : "",
]);
}
print &ui_columns_end();
}
# Find sub-partitions if available
my ($slice) =
grep { $_->{'number'} eq $part_num } @{ $disk->{'slices'} || [] };
my $sub_part_info =
( $slice && scalar( @{ $slice->{'parts'} || [] } ) > 0 )
? join( ", ", map { $_->{'letter'} } @{ $slice->{'parts'} } )
: "-";
push( @cols, &html_escape($sub_part_info) );
push( @cols, $ext );
push( @cols, $entry->{'start'} );
push( @cols, $entry->{'start'} + $entry->{'size'} - 1 );
push( @cols, $entry->{'size_human'} );
# Classify format/use/role via library helper
my ( $format_type, $usage, $role ) = classify_partition_row(
base_device => $base_device,
scheme => ( $disk_structure->{'scheme'} || '' ),
part_num => $part_num,
part_name => $part_name,
part_label => $part_label,
entry_part_type => (
$part_info ? $part_info->{'type'} : $entry->{'part_type'}
),
entry_rawtype =>
( $part_info ? $part_info->{'rawtype'} : undef ),
size_human => $entry->{'size_human'},
size_blocks => $entry->{'size'},
zfs_devices => $zfs_devices,
);
push( @cols, &html_escape( $format_type || '-' ) );
push( @cols, &html_escape( $usage || $text{'part_nouse'} ) );
push( @cols, &html_escape( $role || '-' ) );
}
print &ui_columns_row( \@cols );
}
print &ui_columns_end();
}
else {
print "<b>$text{'disk_none'}</b><p>\n";
}
print &ui_links_row(\@links);
if ( @{ $disk->{'slices'} || [] } ) {
print &ui_links_row( \@links );
print &ui_columns_start(
[
$text{'disk_no'}, $text{'disk_type'},
$text{'disk_extent'}, $text{'disk_start'},
$text{'disk_end'}, $text{'disk_use'},
]
);
foreach my $s ( @{ $disk->{'slices'} } ) {
my @cols = ();
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $s->{'startblock'} - 1 );
$ext .= sprintf "<img src='images/%s.gif' height='10' width='%d'>",
( $s->{'extended'} ? "ext" : "use" ),
$scale * ( $s->{'blocks'} );
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $disk_blocks - $s->{'startblock'} - $s->{'blocks'} );
my $url = "edit_slice.cgi?device=$device_url&amp;slice="
. &urlize( $s->{'number'} );
push( @cols,
"<a href='$url'>" . &html_escape( $s->{'number'} ) . "</a>" );
push( @cols,
&get_type_description( $s->{'type'} ) || $s->{'type'} );
push( @cols, $ext );
push( @cols, $s->{'startblock'} );
push( @cols, $s->{'startblock'} + $s->{'blocks'} - 1 );
my @st = &fdisk::device_status( $s->{'device'} );
my $use = &fdisk::device_status_link(@st);
push( @cols, &html_escape( $use || $text{'part_nouse'} ) );
print &ui_columns_row( \@cols );
}
print &ui_columns_end();
}
else {
print "<p>$text{'disk_none'}</p>\n";
}
}
print &ui_links_row( \@links );
print &ui_hr();
print &ui_buttons_start();
# Show SMART status link if available
if ( &has_command("smartctl") ) {
print &ui_hr();
print &ui_buttons_start();
print &ui_buttons_row( "smart.cgi", $text{'disk_smart'},
$text{'disk_smartdesc'}, &ui_hidden( "device", $device ) );
print &ui_buttons_end();
}
if (&foreign_installed("smart-status")) {
print &ui_buttons_row(
"../smart-status/index.cgi",
$text{'disk_smart'},
$text{'disk_smartdesc'},
&ui_hidden("drive", $disk->{'device'}.":"));
}
print &ui_buttons_end();
&ui_print_footer("", $text{'index_return'});
# Debug: ZFS cache detail
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs_cache'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Pools: " . join( ", ", keys %$zfs_pools ) . "\n\nDevices:\n";
foreach my $device_id ( sort keys %$zfs_devices ) {
next if $device_id =~ /^_debug_/;
my $device_info = $zfs_devices->{$device_id};
print
"$device_id => Pool: $device_info->{'pool'}, Type: $device_info->{'vdev_type'}, Mirrored: "
. ( $device_info->{'is_mirrored'} ? "Yes" : "No" )
. ", RAIDZ: "
. ( $device_info->{'is_raidz'}
? "Yes (Level: $device_info->{'raidz_level'})"
: "No" )
. ", Single: "
. ( $device_info->{'is_single'} ? "Yes" : "No" )
. ", Striped: "
. ( $device_info->{'is_striped'} ? "Yes" : "No" ) . "\n";
}
print "</pre>";
print "</div></div>";
print "</div>";
}
&ui_print_footer( "", $text{'disk_return'} );

View File

@@ -1,87 +1,181 @@
#!/usr/local/bin/perl
# Show details of a partition, with buttons to create a filesystem
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
# Load required libraries
require "./bsdfdisk-lib.pl";
our ( %in, %text, $module_name );
ReadParse();
&ui_print_header($part->{'desc'}, $text{'part_title'}, "");
# Cache input parameters to avoid repeated hash lookups
my $device = $in{'device'};
my $slice_num = $in{'slice'};
my $part_letter = $in{'part'};
my $url_device = &urlize($device);
my $url_slice = &urlize($slice_num);
my $url_part = &urlize($part_letter);
# Get the disk and slice using first() to stop at the first matching element
my @disks = list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error( $text{'disk_edevice'} );
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} );
$in{'slice'} =~ /^\d+$/ or error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or error( $text{'part_egone'} );
my $disk;
foreach my $d (@disks) {
if ( $d->{'device'} eq $device ) {
$disk = $d;
last;
}
}
$disk or error( $text{'disk_egone'} );
my $slice;
foreach my $s ( @{ $disk->{'slices'} } ) {
if ( $s->{'number'} eq $slice_num ) {
$slice = $s;
last;
}
}
$slice or error( $text{'slice_egone'} );
my $part;
foreach my $p ( @{ $slice->{'parts'} } ) {
if ( $p->{'letter'} eq $part_letter ) { $part = $p; last; }
}
$part or error( $text{'part_egone'} );
ui_print_header( $part->{'desc'}, $text{'part_title'}, "" );
# Check if this is a boot partition
my $is_boot = is_boot_partition($part);
if ($is_boot) {
print ui_alert_box( $text{'part_bootdesc'}, 'info' );
}
# Show current details
my @st = &fdisk::device_status($part->{'device'});
my $use = &fdisk::device_status_link(@st);
my $canedit = !@st || !$st[2];
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'})."\n".
&ui_hidden("part", $in{'part'})."\n";
my $zfs_info = get_all_zfs_info();
my @st = fdisk::device_status( $part->{'device'} );
# calculate $use from either ZFS info or from a status link
my $device_path = $part->{'device'};
my $use = $zfs_info->{$device_path} || fdisk::device_status_link(@st);
my $canedit = ( !@st && !$zfs_info->{$device_path} && !$is_boot );
# Prepare hidden fields once
my $hiddens =
ui_hidden( "device", $device ) . "\n"
. ui_hidden( "slice", $slice_num ) . "\n"
. ui_hidden( "part", $part_letter ) . "\n";
if ($canedit) {
print &ui_form_start("save_part.cgi", "post");
print $hiddens;
}
print &ui_table_start($text{'part_header'}, undef, 2);
print &ui_table_row($text{'part_device'},
"<tt>$part->{'device'}</tt>");
print &ui_table_row($text{'part_size'},
&nice_size($part->{'size'}));
print &ui_table_row($text{'part_start'},
$part->{'startblock'});
print &ui_table_row($text{'part_end'},
$part->{'startblock'} + $part->{'blocks'} - 1);
print ui_form_start( "save_part.cgi", "post" ), $hiddens;
}
print ui_table_start( $text{'part_header'}, undef, 2 );
print ui_table_row( $text{'part_device'},
"<tt>" . html_escape( $part->{'device'} ) . "</tt>" );
my $part_bytes = bytes_from_blocks( $part->{'device'}, $part->{'blocks'} );
print ui_table_row( $text{'part_size'},
$part_bytes ? safe_nice_size($part_bytes) : '-' );
print ui_table_row( $text{'part_start'}, $part->{'startblock'} );
print ui_table_row( $text{'part_end'},
$part->{'startblock'} + $part->{'blocks'} - 1 );
my $disk_geom = get_detailed_disk_info( $disk->{'device'} );
my $stripesize =
( $disk_geom && $disk_geom->{'stripesize'} )
? $disk_geom->{'stripesize'}
: '-';
print ui_table_row( $text{'disk_stripesize'}, $stripesize );
if ($canedit) {
print &ui_table_row($text{'part_type'},
&ui_select("type", $part->{'type'},
[ &list_partition_types() ], 1, 0, 1));
}
# BSD disklabel partitions only support FreeBSD types
print ui_table_row(
$text{'part_type'},
ui_select(
"type", $part->{'type'}, [ list_partition_types('BSD') ],
1, 0, 1
)
);
}
else {
print &ui_table_row($text{'part_type'},
$part->{'type'});
}
print ui_table_row( $text{'part_type'}, get_format_type($part) );
}
my $use_text = (
( !@st && !$zfs_info->{ $part->{'device'} } ) ? $text{'part_nouse'}
: (
( $st[2] || $zfs_info->{ $part->{'device'} } )
? text( 'part_inuse', $use )
: text( 'part_foruse', $use )
)
);
print ui_table_row( $text{'part_use'}, $use_text );
print &ui_table_row($text{'part_use'},
!@st ? $text{'part_nouse'} :
$st[2] ? &text('part_inuse', $use) :
&text('part_foruse', $use));
print &ui_table_end();
# Add a row for the partition role
print ui_table_row( $text{'part_role'}, get_partition_role($part) );
print ui_table_end();
if ($canedit) {
print &ui_form_end([ [ undef, $text{'save'} ] ]);
}
print ui_form_end( [ [ undef, $text{'save'} ] ] );
}
# Show newfs and mount buttons
# Existing partitions on this slice
if ( @{ $slice->{'parts'} || [] } ) {
my $zfs = get_all_zfs_info();
print ui_hr();
print ui_columns_start(
[
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'},
$text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'},
$text{'slice_role'}
],
$text{'epart_existing'}
);
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $slice->{'parts'} } )
{
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
my @stp = fdisk::device_status( $p->{'device'} );
my $usep =
$zfs->{ $p->{'device'} }
|| fdisk::device_status_link(@stp)
|| $text{'part_nouse'};
my $rolep = get_partition_role($p);
my $pb2 = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
print ui_columns_row(
[
uc( $p->{'letter'} ),
&html_escape($ptype),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
( $pb2 ? safe_nice_size($pb2) : '-' ),
$usep,
&html_escape($rolep)
]
);
}
print ui_columns_end();
}
# Show newfs and mount buttons if editing is allowed
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
&show_filesystem_buttons($hiddens, \@st, $part);
print &ui_buttons_row(
"delete_part.cgi", $text{'part_delete'},
$text{'part_deletedesc'}, $hiddens);
print &ui_buttons_end();
}
print ui_hr();
print ui_buttons_start();
my $mount_return =
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part";
show_filesystem_buttons( $hiddens, \@st, $part, $mount_return );
print ui_buttons_row( "delete_part.cgi", $text{'part_delete'},
$text{'part_deletedesc'}, $hiddens );
print ui_buttons_end();
}
else {
print "<b>$text{'part_cannotedit'}</b><p>\n";
}
print $is_boot
? "<b>$text{'part_bootcannotedit'}</b><p>\n"
: "<b>$text{'part_cannotedit'}</b><p>\n";
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
# SMART button (physical device)
if ( &has_command("smartctl") ) {
print ui_hr();
print ui_buttons_start();
print ui_buttons_row( "smart.cgi", $text{'disk_smart'},
$text{'disk_smartdesc'}, ui_hidden( "device", $disk->{'device'} ) );
print ui_buttons_end();
}
ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -1,146 +1,285 @@
#!/usr/local/bin/perl
# Show details of a slice, and partitions on it
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
our ( %in, %text, $module_name );
ReadParse();
my $extwidth = 300;
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my @disks = list_disks_partitions();
&ui_print_header($slice->{'desc'}, $text{'slice_title'}, "");
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error( $text{'disk_edevice'} );
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} );
$in{'slice'} =~ /^\d+$/ or error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks
or error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} }
or error( $text{'slice_egone'} );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
ui_print_header( $slice->{'desc'}, $text{'slice_title'}, "" );
# Show slice details
my @st = &fdisk::device_status($slice->{'device'});
my $use = &fdisk::device_status_link(@st);
my $canedit = !@st || !$st[2];
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'})."\n";
print &ui_form_start("save_slice.cgi");
my $zfs_info = get_all_zfs_info();
my ( $zfs_pools, $zfs_devices ) = build_zfs_devices_cache();
# Cache slice device status
my @slice_status = fdisk::device_status( $slice->{'device'} );
my $slice_use =
$zfs_info->{ $slice->{'device'} }
? $zfs_info->{ $slice->{'device'} }
: fdisk::device_status_link(@slice_status);
my $canedit = ( !@slice_status || !$slice_status[2] );
# Prepare hidden fields
my $hiddens = ui_hidden( "device", $in{'device'} ) . "\n"
. ui_hidden( "slice", $in{'slice'} ) . "\n";
# Derive disk scheme for classifier
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
# Device label (GPT label or glabel)
my $slice_label = get_device_label_name(
disk => $disk,
slice => $slice,
disk_structure => $disk_structure
);
# Check if this is a boot slice
my $is_boot = is_boot_partition($slice);
print ui_alert_box( $text{'slice_bootdesc'}, 'info' ) if $is_boot;
my $confirm_msg = $text{'confirm_overwrite'}
|| 'You will destroy/overwrite existing data structures. Continue?';
my $confirm_js = $confirm_msg;
$confirm_js =~ s/\\/\\\\/g;
$confirm_js =~ s/'/\\'/g;
$confirm_js =~ s/\r?\n/\\n/g;
print ui_form_start( "save_slice.cgi", "post", undef,
"onsubmit=\"return confirm('$confirm_js')\"" );
print $hiddens;
print &ui_table_start($text{'slice_header'}, undef, 2);
print ui_table_start( $text{'slice_header'}, undef, 2 );
print ui_table_row( $text{'part_device'},
"<tt>" . html_escape( $slice->{'device'} ) . "</tt>" );
print ui_table_row( $text{'slice_label'},
$slice_label ? "<tt>" . html_escape($slice_label) . "</tt>" : "-" );
my $slice_bytes = bytes_from_blocks( $slice->{'device'}, $slice->{'blocks'} );
print ui_table_row( $text{'slice_ssize'},
$slice_bytes ? safe_nice_size($slice_bytes) : '-' );
print ui_table_row( $text{'slice_sstart'}, $slice->{'startblock'} );
print ui_table_row( $text{'slice_send'},
$slice->{'startblock'} + $slice->{'blocks'} - 1 );
print &ui_table_row($text{'part_device'},
"<tt>$slice->{'device'}</tt>");
# Slice type selector (GPT vs legacy)
if ( is_using_gpart() ) {
my $scheme =
( $disk_structure && $disk_structure->{'scheme'} )
? $disk_structure->{'scheme'}
: 'GPT';
my @opts = list_partition_types($scheme);
print &ui_table_row($text{'slice_ssize'},
&nice_size($slice->{'size'}));
print &ui_table_row($text{'slice_sstart'},
$slice->{'startblock'});
print &ui_table_row($text{'slice_send'},
$slice->{'startblock'} + $slice->{'blocks'} - 1);
print &ui_table_row($text{'slice_stype'},
&ui_select("type", $slice->{'type'},
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
print &ui_table_row($text{'slice_sactive'},
$slice->{'active'} ? $text{'yes'} :
&ui_yesno_radio("active", $slice->{'active'}));
print &ui_table_row($text{'slice_suse'},
!@st ? $text{'part_nouse'} :
$st[2] ? &text('part_inuse', $use) :
&text('part_foruse', $use));
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
print &ui_hr();
# Show partitions table
my @links = ( "<a href='part_form.cgi?device=".&urlize($disk->{'device'}).
"&slice=$in{'slice'}'>".$text{'slice_add'}."</a>" );
if (@{$slice->{'parts'}}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'slice_letter'},
$text{'slice_type'},
$text{'slice_extent'},
$text{'slice_size'},
$text{'slice_start'},
$text{'slice_end'},
$text{'slice_use'},
]);
foreach my $p (@{$slice->{'parts'}}) {
# Create images for the extent
my $ext = "";
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($p->{'startblock'} - 1) /
$slice->{'blocks'};
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
$p->{'extended'} ? "ext" : "use",
$extwidth*($p->{'blocks'}) /
$slice->{'blocks'};
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($slice->{'blocks'} - $p->{'startblock'} -
$p->{'blocks'}) / $slice->{'blocks'};
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
# Add row for the partition
my $url = "edit_part.cgi?device=".&urlize($disk->{'device'}).
"&slice=".$slice->{'number'}."&part=".$p->{'letter'};
print &ui_columns_row([
"<a href='$url'>".uc($p->{'letter'})."</a>",
"<a href='$url'>$p->{'type'}</a>",
$ext,
&nice_size($p->{'size'}),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$use,
]);
}
print &ui_columns_end();
print &ui_links_row(\@links);
}
# Default sensibly per scheme
my $default_type = ( $scheme =~ /GPT/i ) ? 'freebsd-zfs' : 'freebsd';
print ui_table_row( $text{'slice_stype'},
ui_select( "type", $slice->{'type'} || $default_type, \@opts ) );
}
else {
# No partitions yet
if (@st) {
# And directly in use, so none can be created
print "<b>$text{'slice_none2'}</b><p>\n";
}
else {
# Show link to add first partition
print "<b>$text{'slice_none'}</b><p>\n";
print &ui_links_row(\@links);
}
}
# Pre-cache tag options for the slice type select (legacy fdisk)
my @tags = fdisk::list_tags();
my @tag_options = map { [ $_, fdisk::tag_name($_) ] } @tags;
@tag_options = sort { $a->[1] cmp $b->[1] } @tag_options;
print ui_table_row( $text{'slice_stype'},
ui_select( "type", $slice->{'type'}, \@tag_options ) );
}
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
# Active slice - only applicable for legacy MBR. For GPT/UEFI and for EFI/freebsd-boot types, the active flag is irrelevant.
my $is_gpt =
is_using_gpart()
&& ( $disk_structure
&& $disk_structure->{'scheme'}
&& $disk_structure->{'scheme'} =~ /GPT/i );
if ( !$is_gpt && ( $slice->{'type'} !~ /^(?:efi|freebsd-boot)$/i ) ) {
my $active_default = $slice->{'active'} ? 1 : 0;
print ui_table_row( $text{'slice_sactive'},
ui_yesno_radio( "active", $active_default ) );
}
else {
# Do not offer the control; display 'No' since active is not used here
print ui_table_row( $text{'slice_sactive'}, $text{'no'} );
}
print ui_table_row(
$text{'slice_suse'},
( !$slice_use || $slice_use eq $text{'part_nouse'} ) ? $text{'part_nouse'}
: ( $slice_status[2] ? text( 'part_inuse', $slice_use )
: text( 'part_foruse', $slice_use ) )
);
if (!@{$slice->{'parts'}}) {
&show_filesystem_buttons($hiddens, \@st, $slice);
}
# Add a row for the slice role
print ui_table_row( $text{'slice_role'}, get_partition_role($slice) );
print ui_table_end();
print ui_form_end( [ [ undef, $text{'save'} ] ] );
print ui_hr();
# Button to delete slice
print &ui_buttons_row(
'delete_slice.cgi',
$text{'slice_delete'},
$text{'slice_deletedesc'},
&ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'}));
# Show partitions table (only for MBR slices that support BSD disklabel)
my $can_have_parts = 0;
if ( !is_using_gpart() ) {
print &ui_buttons_end();
}
# Legacy MBR with BSD disklabel
$can_have_parts = 1;
}
elsif ($disk_structure
&& $disk_structure->{'scheme'}
&& $disk_structure->{'scheme'} !~ /GPT/i )
{
# MBR-style slice
$can_have_parts = 1;
}
my @links =
$can_have_parts
? ( "<a href='part_form.cgi?device="
. $url_device
. "&slice=$url_slice'>"
. $text{'slice_add'}
. "</a>" )
: ();
if ( @{ $slice->{'parts'} } ) {
print ui_links_row( \@links ) if @links;
print ui_columns_start(
[
$text{'slice_letter'}, $text{'slice_type'},
$text{'slice_extent'}, $text{'slice_size'},
$text{'slice_start'}, $text{'slice_end'},
$text{'disk_stripesize'}, $text{'slice_use'},
$text{'slice_role'},
]
);
# Pre-calculate scaling factor for the partition extent images
my $scale = $extwidth / $slice->{'blocks'};
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
foreach my $p ( @{ $slice->{'parts'} } ) {
# Create images representing the partition extent
my $gap_before = sprintf( "<img src=images/gap.gif height=10 width=%d>",
int( $scale * ( $p->{'startblock'} - 1 ) ) );
my $img_type = $p->{'extended'} ? "ext" : "use";
my $partition_img =
sprintf( "<img src=images/%s.gif height=10 width=%d>",
$img_type, int( $scale * $p->{'blocks'} ) );
my $gap_after = sprintf(
"<img src=images/gap.gif height=10 width=%d>",
int(
$scale * (
$slice->{'blocks'} - $p->{'startblock'} - $p->{'blocks'}
)
)
);
my $ext = $gap_before . $partition_img . $gap_after;
# Cache partition device status information
my @part_status = fdisk::device_status( $p->{'device'} );
my $part_use = $zfs_info->{ $p->{'device'} }
|| fdisk::device_status_link(@part_status);
# Prefer GEOM details for stripesize
my $ginfo = get_detailed_disk_info( $p->{'device'} );
my $stripesize =
( $ginfo && $ginfo->{'stripesize'} ) ? $ginfo->{'stripesize'} : '-';
# Classify format/use/role via library helper
( my $pn = $p->{'device'} ) =~ s{^/dev/}{};
my ( $fmt, $use_txt, $role_txt ) = classify_partition_row(
base_device => $base_device,
scheme => ( $disk_structure->{'scheme'} || '' ),
part_name => $pn,
entry_part_type => $p->{'type'},
zfs_devices => $zfs_devices,
);
$use_txt ||= $part_use;
$role_txt ||= get_partition_role($p);
# Build edit URL
my $url =
"edit_part.cgi?device="
. $url_device
. "&slice="
. $url_slice
. "&part="
. &urlize( $p->{'letter'} );
my $psz_b = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
print ui_columns_row(
[
"<a href='$url'>" . uc( $p->{'letter'} ) . "</a>",
"<a href='$url'>"
. html_escape( $fmt || get_format_type($p) ) . "</a>",
$ext,
( $psz_b ? safe_nice_size($psz_b) : '-' ),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$stripesize,
$use_txt,
html_escape($role_txt),
]
);
}
print ui_columns_end();
print ui_links_row( \@links ) if @links;
}
else {
# GPT partitions do not have sub-partitions
if ( !$can_have_parts ) {
# No message needed for GPT; partitions are top-level
}
# If slice is in use by a filesystem OR it is a boot slice, do not allow creating partitions
elsif ( @slice_status || $zfs_info->{ $slice->{'device'} } || $is_boot ) {
print "<b>$text{'slice_none2'}</b><p>\n";
}
else {
print "<b>$text{'slice_none'}</b><p>\n";
print ui_links_row( \@links ) if @links;
}
}
if ( $canedit && !$is_boot ) { # Do not allow editing boot slices
print ui_hr();
print ui_buttons_start();
if ( !@{ $slice->{'parts'} } ) {
my $mount_return =
"edit_slice.cgi?device=$url_device&slice=$url_slice";
show_filesystem_buttons( $hiddens, \@slice_status, $slice,
$mount_return );
}
print ui_buttons_row(
'change_slice_label.cgi',
$text{'slice_chglabel'},
$text{'slice_chglabeldesc'},
ui_hidden( "device", $in{'device'} ) . "\n"
. ui_hidden( "slice", $in{'slice'} )
);
print ui_buttons_row(
'delete_slice.cgi',
$text{'slice_delete'},
$text{'slice_deletedesc'},
ui_hidden( "device", $in{'device'} ) . "\n"
. ui_hidden( "slice", $in{'slice'} )
);
print ui_buttons_end();
}
# SMART button (physical device)
if ( &has_command("smartctl") ) {
print ui_hr();
print ui_buttons_start();
print ui_buttons_row( "smart.cgi", $text{'disk_smart'},
$text{'disk_smartdesc'}, ui_hidden( "device", $disk->{'device'} ) );
print ui_buttons_end();
}
ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

View File

@@ -6,57 +6,107 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'fsck_err'});
&error_setup( $text{'fsck_err'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} ) if $in{'part'};
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($object, $part);
if ($in{'part'} ne '') {
($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
else {
$object = $slice;
}
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
&ui_print_unbuffered_header($object->{'desc'}, $text{'fsck_title'}, "");
# Do the creation
print &text('fsck_checking', "<tt>$object->{'device'}</tt>"),"<br>\n";
print "<pre>\n";
my $cmd = &get_check_filesystem_command($disk, $slice, $part);
&additional_log('exec', undef, $cmd);
my $fh = "CMD";
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
print "</pre>";
if ($?) {
print $text{'fsck_failed'},"<p>\n";
}
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
print $text{'fsck_done'},"<p>\n";
}
&webmin_log("fsck", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
$object = $slice;
}
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
# Safety checks: do not run fsck on boot partitions or in-use devices
if ( is_boot_partition($object) ) {
&error( $in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'} );
}
my @st_obj = &fdisk::device_status( $object->{'device'} );
my $use_obj = &fdisk::device_status_link(@st_obj);
if ( @st_obj && $st_obj[2] ) {
&error( &text( 'part_esave', &html_escape($use_obj) ) );
}
&ui_print_unbuffered_header( $object->{'desc'}, $text{'fsck_title'}, "" );
# If device is ZFS, do not run fsck; show zpool status instead
my $zmap = get_all_zfs_info();
if ( $zmap->{ $object->{'device'} } ) {
my $pool = $zmap->{ $object->{'device'} };
$pool =~ s/^.*?\b([A-Za-z0-9_\-]+)\b.*$/$1/;
print &text(
'fsck_checking', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
),
"<br>\n";
print "<pre>\n";
my $cmd = "zpool status 2>&1";
&additional_log( 'exec', undef, $cmd );
print &html_escape( &backquote_command($cmd) );
print "</pre>";
print $text{'fsck_done'}, "<p>\n";
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
# Do the creation
print &text(
'fsck_checking', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
),
"<br>\n";
print "<pre>\n";
my $cmd = &get_check_filesystem_command( $disk, $slice, $part );
&additional_log( 'exec', undef, $cmd );
my $out = &backquote_command( $cmd . " 2>&1" );
foreach my $line ( split( /\n/, $out ) ) {
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
print &html_escape($line) . "\n";
}
print "</pre>";
my $rc = $? >> 8;
if ( $rc == 0 ) {
print $text{'fsck_done'}, "<p>\n";
}
elsif ( $rc == 1 ) {
print $text{'fsck_done'}, "<p>\n";
print $text{'fsck_fixed'}, "<p>\n" if ( $text{'fsck_fixed'} );
}
elsif ( $rc == 2 ) {
print $text{'fsck_done'}, "<p>\n";
print $text{'fsck_reboot'}, "<p>\n" if ( $text{'fsck_reboot'} );
}
else {
print $text{'fsck_failed'}, "<p>\n";
}
}
&webmin_log( "fsck", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object );
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

BIN
bsdfdisk/images/free.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

View File

@@ -1,41 +1,57 @@
#!/usr/local/bin/perl
# Show a list of disks
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, %config, $module_name);
&ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0,
&help_search_link("fdisk", "man"));
my $err = &check_fdisk();
# Check prerequisites first
my $err = check_fdisk();
if ($err) {
&ui_print_endpage(&text('index_problem', $err));
}
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0);
print "<b>$text{'index_problem'}</b><br>\n$err\n";
ui_print_footer("/", $text{'index_return'});
exit;
}
# Print header with help link
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0,
help_search_link("fdisk", "man"));
# List and sort disks by device name
my @disks = list_disks_partitions();
@disks = sort { ($a->{'device'}//'') cmp ($b->{'device'}//'') } @disks;
my @disks = &list_disks_partitions();
@disks = sort { $a->{'device'} cmp $b->{'device'} } @disks;
if (@disks) {
print &ui_columns_start([ $text{'index_dname'},
$text{'index_dsize'},
$text{'index_dmodel'},
$text{'index_dparts'} ]);
foreach my $d (@disks) {
print &ui_columns_row([
"<a href='edit_disk.cgi?device=".&urlize($d->{'device'}).
"'>".&partition_description($d->{'device'})."</a>",
&nice_size($d->{'size'}),
$d->{'model'},
scalar(@{$d->{'slices'}}),
]);
}
print &ui_columns_end();
}
else {
print "<b>$text{'index_none'}</b> <p>\n";
}
print ui_columns_start([
$text{'index_dname'},
$text{'index_dsize'},
$text{'index_dmodel'},
$text{'index_dparts'}
]);
&ui_print_footer("/", $text{'index'});
foreach my $d (@disks) {
my $device = $d->{'device'} // '';
my $disk_name = $device; $disk_name =~ s{^/dev/}{};
# Prefer mediasize from gpart list (bytes); fallback to diskinfo size
my $base = $device; $base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
my $bytes = $ds && $ds->{'mediasize'} ? $ds->{'mediasize'} : $d->{'size'};
my $size_display = defined $bytes ? safe_nice_size($bytes) : '-';
my $model = $d->{'model'} // '-';
my $url_device = urlize($device);
my $slices_cnt = scalar(@{ $d->{'slices'} || [] });
print ui_columns_row([
"<a href='edit_disk.cgi?device=$url_device'>$disk_name</a>",
$size_display,
$model, # Now correctly populated from bsdfdisk-lib.pl
$slices_cnt,
]);
}
print ui_columns_end();
}
else {
print "<b>$text{'index_none'}</b><p>\n";
}
ui_print_footer("/", $text{'index_return'});

View File

@@ -1,42 +1,92 @@
index_title=Partitions on Local Disks
index_ecmd=The required command $1 is missing
index_problem=This module cannot be used : $1
index_none=No disks were found on this system!
index_ecmd=Missing required command $1
index_problem=Cannot use this module : $1
index_none=No disks found on this system!
index_dname=Disk name
index_dsize=Total size
index_dmodel=Make and model
index_dparts=Slices
index_return=list of disks
index_format=Format
index_efdisk=You must have $1 or $2 installed to use this module
disk_edevice=Invalid device parameter
nslice_etype=Invalid slice type
# Disk overview
disk_title=Edit Disk
disk_egone=Disk no longer exists!
disk_no=Slice
disk_type=Type
disk_extent=Extent
disk_start=Start block
disk_end=End block
disk_use=Used by
disk_scount=$1 partitions
disk_parts=Partitions
disk_free=Free space
disk_vm=Virtual memory
disk_iscsi=iSCSI shared device $1
disk_none=This disk has no slices yet.
disk_size=Size
disk_dsize=<b>Disk size:</b> $1
disk_model=<b>Make and model:</b> $1
disk_cylinders=<b>Cylinders:</b> $1
disk_blocks=<b>Blocks:</b> $1
disk_device=<b>Device file:</b> $1
disk_dsize=Disk size
disk_model=Make and model
disk_cylinders=Cylinders
disk_blocks=Blocks
disk_device=Device file
disk_return=disk details and list of slices
disk_add=Create a new slice.
disk_smart=Show SMART Status
disk_smartdesc=Show the current status of this drive as detected by SMART, and check it for disk errors.
disk_no=No.
disk_partno=Part. No.
disk_partname=Part. Name
disk_partlabel=Part. Label
disk_subpart=Sub-part.
disk_size=Size
disk_free_space=Free Space
disk_available=Available
disk_format=Format
disk_use=Used by
disk_role=Role Type
select_device=$1 device $2
select_slice=$1 device $2 slice $3
select_part=$1 device $2 slice $3 partition $4
# Debug and scheme
disk_show_debug=Show raw debug information
disk_hide_debug=Hide debug information
disk_scheme=Partition Scheme
disk_unknown=Unknown
disk_sectorsize=Sector Size
disk_bytes=bytes
# GEOM information
disk_geom_header=GEOM Information
disk_geom_details=GEOM Disk Details
disk_mediasize=Media Size
disk_stripesize=Stripe Size
disk_stripeoffset=Stripe Offset
disk_mode=Mode
disk_rotationrate=Rotation Rate
disk_ident=Identifier
disk_lunid=LUN ID
disk_descr=Description
disk_rpm=RPM
disk_ssd=SSD (Solid State Drive)
# Debug panels
disk_debug_gpart=GPART Output
disk_debug_structure=Parsed Disk Structure
disk_debug_geom=GEOM Disk List
disk_debug_zfs=ZFS Status
disk_debug_zfs_cache=ZFS Devices Cache
# Roles and usage
disk_inzfs=In ZFS pool
disk_zfs_log=ZFS Log Device
disk_zfs_cache=ZFS Cache Device
disk_zfs_spare=ZFS Spare Device
disk_zfs_mirror=ZFS Mirror
disk_zfs_stripe=ZFS Stripe
disk_zfs_single=ZFS Single Device
disk_zfs_data=ZFS Data Device
disk_boot=Boot Partition
disk_boot_role=System Boot
disk_swap=Swap
disk_swap_role=Virtual Memory
# Slice pages
slice_title=Edit Slice
slice_egone=Selected slice does not exist!
slice_ssize=Slice size
@@ -56,55 +106,83 @@ slice_use=Used by
slice_none=This slice has no partitions yet.
slice_none2=This slice has no partitions, and none can be created as it is in use as a filesystem.
slice_delete=Delete Slice
slice_deletedesc=Delete this slice and all partitions and filesystems within it. Any data on those filesystem will be almost certainly unrecoverable.
slice_deletedesc=Delete this slice and all partitions and filesystems within it. Any data on that filesystem will be almost certainly unrecoverable.
slice_return=slice details and list of partitions
slice_err=Failed to modify slice
slice_header=Slice details
slice_suse=Directly used by
slice_label=Device label
slice_adddesc=Create a new partition within this slice.
slice_cannotedit=This slice cannot be modified as it is currently in use.
slice_eboot=Cannot delete this slice as it contains boot partitions
slice_bootdesc=This slice contains bootloader code or kernel files needed to start the system
slice_role=Role
slice_chglabel=Change device label
slice_chglabeldesc=Set or update the label for this slice.
slice_label_title=Change Slice Label
slice_label_header=Change device label
slice_label_current=Current label
slice_label_new=New label
slice_label_err=Failed to change slice label
slice_label_empty=Label cannot be empty
slice_label_noglabel=glabel command not available to set labels on this disk scheme
# Delete slice dialogs
dslice_title=Delete Slice
dslice_rusure=Are you sure you want to delete the slice $1? Any partitions and filesystems within it will also be deleted.
dslice_rusure=Are you sure you want to delete the slice $1 ? Any partitions and filesystems within it will also be deleted.
dslice_warn=Warning - this slice is currently used by : $1
dslice_confirm=Delete Now
dslice_deleting=Deleting slice $1 ..
dslice_failed=.. deletion failed : $1
dslice_done=.. done
# Create slice
nslice_title=Create Slice
nslice_header=New slice details
nslice_number=Slice number
nslice_autonext=Will auto-select next index
nslice_diskblocks=Disk size in blocks
nslice_start=Starting block
nslice_end=Ending block
nslice_type=New slice type
nslice_makepart=Create default partition?
nslice_type=Slice type
nslice_err=Failed to create slice
nslice_enumber=Missing or non-numeric slice number
nslice_enumber=Slice number must be a number
nslice_eclash=A slice with number $1 already exists
nslice_estart=Starting block must be a number
nslice_eend=Ending block must be a number
nslice_erange=Starting block must be lower than the ending block
nslice_emax=Ending block cannot be larger than the disk size of $1 blocks
nslice_erange=Starting block must be before ending block
nslice_emax=Ending block cannot be larger than disk size of $1 blocks
nslice_creating=Creating slice $1 on $2 ..
nslice_failed=.. slice creation failed : $1
nslice_failed=.. creation failed : $1
nslice_done=.. slice added
nslice_parting=Create default partitions in slice $1 on $2 ..
nslice_pfailed=.. partition creation failed : $1
nslice_pdone=.. partition added
nslice_existing_header=Existing slices on this disk
nslice_existing_parts=Existing partitions on this disk
nslice_existing_slices_label=Existing slices
nslice_existing_parts_label=Existing partitions
nslice_enospace=No space on device left to create a slice!
epart_existing=Existing partitions on this slice
# Create partition
npart_title=Create Partition
npart_header=New partition details
npart_letter=Partition letter
npart_diskblocks=Slice size in blocks
npart_slicerel=(slice-relative)
npart_creserved=partition 'c' is reserved
npart_type=Partition type
npart_err=Failed to create partition
npart_eletter=Partition number must be a letter from A to D
npart_eletter=Partition letter must be a-h (excluding c)
npart_ereserved=Partition 'c' is reserved for the whole slice in BSD disklabels
npart_eclash=A partition with letter $1 already exists
npart_emax=Ending block cannot be larger than the slice size of $1 blocks
npart_emax=Ending block cannot be larger than slice size of $1 blocks
npart_erange=Start block must be less than end block
npart_creating=Creating partition $1 on $2 ..
npart_failed=.. partition creation failed : $1
npart_done=.. partition added
# Edit partition
part_title=Edit Partition
part_egone=Partition no longer exists!
part_header=Partition details
@@ -118,9 +196,9 @@ part_nouse=Nothing
part_inuse=In use by $1
part_foruse=For use by $1
part_newfs=Create Filesystem
part_newfsdesc=Click this button to create a new UFS filesystem on this device. Any data that was previously on the partition will be erased.
part_newfsdesc=Click this button to create a new filesystem on this device. Will destroy/overwrite previous filesystem if it's not a pool!
part_fsck=Check Filesystem
part_fsckdesc=Click this button to check the UFS filesystem on this device, and repair any errors found.
part_fsckdesc=Click this button to check the filesystem on this device, and repair any errors found.
part_delete=Delete Partition
part_deletedesc=Click this button to remove this partition from the slice. Any data on the partition will be lost forever.
part_return=partition details
@@ -128,18 +206,29 @@ part_err=Failed to save partition
part_esave=Currently in use by $1
part_newmount=Mount Partition On:
part_newmount2=Mount Partition
part_mountmsg=Mount this device on new directory on your system, so that it can be used to store files. A filesystem must have been already created on the device.
part_mountmsg2=Mount this device as virtual memory on your system, to increase the amount of memory available.
part_mountmsg=Mount this device on a new directory on your system, so that it can be used to store files. A filesystem must already be created on the device.
part_mountmsg2=Mount this device as virtual memory on your system to increase the amount of available memory.
part_cannotedit=This partition cannot be modified as it is currently in use.
part_boot=Boot Partition
part_eboot=Cannot delete boot partitions as this may render the system unbootable
part_bootdesc=This partition contains bootloader code or kernel files needed to start the system
part_zfslog=ZFS Log Device
part_zfsdata=ZFS Data Device
part_mounted=Mounted on $1
part_unused=Not in use
part_bootcannotedit=This partition cannot be modified as it is a boot partition. Changing it could render the system unbootable.
part_role=Role
# Delete partition dialogs
dpart_title=Delete Partition
dpart_rusure=Are you sure you want to delete the partition $1? Any filesystems within it will also be deleted.
dpart_rusure=Are you sure you want to delete the partition $1 ? Any filesystems in it will also be deleted.
dpart_warn=Warning - this partition is currently used by $1
dpart_confirm=Delete Now
dpart_deleting=Deleting partition $1 ..
dpart_failed=.. deletion failed : $1
dpart_done=.. done
# New filesystem
newfs_title=Create Filesystem
newfs_header=New filesystem details
newfs_free=Space to reserve for root
@@ -147,27 +236,87 @@ newfs_deffree=Default (8%)
newfs_trim=Enable TRIM mode for SSDs
newfs_label=Filesystem label
newfs_none=None
newfs_create=Create Now
newfs_err=Failed to create filesystem
newfs_efree=Space to reserve for root must be a percentage
newfs_elabel=Missing or invalid label
newfs_efree=Missing or invalid percentage of free space
newfs_elabel=Missing or invalid filesystem label
newfs_creating=Creating filesystem on $1 ..
newfs_failed=.. creation failed!
newfs_done=.. created successfully
newfs_failed=.. creation failed : $1
newfs_done=.. filesystem created
create=Create
newfs_zfs_title=Create ZFS Filesystem
newfs_zfs_header=New ZFS filesystem details
newfs_zfs_parent=Parent file system:
newfs_zfs_existing=Existing filesystems:
newfs_zfs_fs=file system
newfs_zfs_used=used
newfs_zfs_avail=avail
newfs_zfs_refer=refer
newfs_zfs_mount=mountpoint
newfs_zfs_name=Name:
newfs_zfs_mountpoint=Mount point:
newfs_zfs_mount_blank=blank for default
newfs_zfs_opts=File system options
newfs_zfs_tab_fs=Create Filesystem
newfs_zfs_tab_vol=Create Volume
newfs_zfs_aclflags=ACL inherit flags:
newfs_zfs_aclflags_desc=Add :fd to base NFSv4 ACL entries
newfs_zfs_acltype_desc=Controls whether ACLs are enabled and if so what type of ACL to use.
newfs_zfs_aclinherit_desc=Controls how ACL entries are inherited when files and directories are created.
newfs_zfs_aclmode_desc=Controls how an ACL is modified during a chmod operation.
newfs_zfs_err=Failed to create ZFS filesystem
newfs_zfs_nozfs=zfs command not available
newfs_zfs_notinpool=Device is not part of a ZFS pool
newfs_zfs_badname=Invalid dataset name
newfs_zfs_badopt=Invalid value for option $1
newfs_zfs_creating=Creating ZFS filesystem on $1 ..
newfs_zfs_failed=.. creation failed : $1
newfs_zfs_done=.. filesystem created
newfs_zvol_title=Create ZFS Volume
newfs_zvol_header=New ZFS volume details
newfs_zvol_name=Name:
newfs_zvol_size=Size:
newfs_zvol_opts=Volume options
newfs_zvol_existing=Existing volumes:
newfs_zvol_vol=volume
newfs_zvol_volsize=volsize
newfs_zvol_sparse=Sparse:
newfs_zvol_sparse_desc=Create as sparse volume (thin provisioned)
newfs_zvol_refreservation=Refreservation:
newfs_zvol_err=Failed to create ZFS volume
newfs_zvol_badname=Invalid volume name
newfs_zvol_badsize=Invalid volume size
newfs_zvol_badopt=Invalid value for option $1
newfs_zvol_badrefres=Invalid refreservation size
newfs_zvol_creating=Creating ZFS volume on $1 ..
newfs_zvol_failed=.. creation failed : $1
newfs_zvol_done=.. volume created
# FS check
fsck_title=Check Filesystem
fsck_header=Filesystem check options
fsck_repair=Repair mode
fsck_fix=Only fix safe errors
fsck_fix2=Try to fix all errors
fsck_err=Failed to check filesystem
fsck_checking=Checking filesystem on $1 ..
fsck_exec=Executing command $1 ..
fsck_failed=.. check failed!
fsck_done=.. check completed with no errors found
fsck_done=.. check complete
fsck_fixed=.. file system was modified and fixed.
fsck_reboot=.. file system modified; a reboot may be required.
fsck_checking=Checking filesystem on $1 ..
confirm_overwrite=You will destroy/overwrite existing data structures. Continue?
log_create_slice=Created slice $1
log_delete_slice=Deleted slice $1
log_modify_slice=Modified slice $1
log_create_part=Created partition $1
log_delete_part=Deleted partition $1
log_modify_part=Modified partition $1
log_newfs_part=Created filesystem on partition $1
log_fsck_part=Checked filesystem on partition $1
# Logging
action_create_slice=Created slice $1
action_delete_slice=Deleted slice $1
action_modify_slice=Modified slice $1
action_create_part=Created partition $1
action_delete_part=Deleted partition $1
action_modify_part=Modified partition $1
action_create_fs=Created filesystem on $1
action_check_fs=Checked filesystem on $1
__norefs=1
# Generic
save=Save
yes=Yes
no=No

View File

@@ -6,67 +6,123 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'newfs_err'});
&error_setup( $text{'newfs_err'} );
# Get the disk and slice
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} ) if $in{'part'};
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($object, $part);
if ($in{'part'} ne '') {
($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
}
$object = $slice;
}
# Safety checks: do not run newfs on boot partitions or in-use devices
if ( is_boot_partition($object) ) {
&error( $in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'} );
}
my @st_obj = &fdisk::device_status( $object->{'device'} );
my $use_obj = &fdisk::device_status_link(@st_obj);
if ( @st_obj && $st_obj[2] ) {
&error( &text( 'part_esave', &html_escape($use_obj) ) );
}
# Validate inputs
my $newfs = { };
$in{'free_def'} || $in{'free'} =~ /^\d+$/ && $in{'free'} <= 100 ||
&error($text{'newfs_efree'});
my $newfs = {};
$in{'free_def'}
|| ( $in{'free'} =~ /^\d+$/ && $in{'free'} >= 0 && $in{'free'} <= 100 )
|| &error( $text{'newfs_efree'} );
$newfs->{'free'} = $in{'free_def'} ? undef : $in{'free'};
$newfs->{'trim'} = $in{'trim'};
$in{'label_def'} || $in{'label'} =~ /^\S+$/ ||
&error($text{'newfs_elabel'});
$in{'label_def'}
|| length( $in{'label'} ) > 0
|| &error( $text{'newfs_elabel'} );
$newfs->{'label'} = $in{'label_def'} ? undef : $in{'label'};
&ui_print_unbuffered_header($object->{'desc'}, $text{'newfs_title'}, "");
&ui_print_unbuffered_header( $object->{'desc'}, $text{'newfs_title'}, "" );
# Do the creation
print &text('newfs_creating', "<tt>$object->{'device'}</tt>"),"<br>\n";
print &text(
'newfs_creating', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
),
"<br>\n";
print "<pre>\n";
my $cmd = &get_create_filesystem_command($disk, $slice, $part, $newfs);
&additional_log('exec', undef, $cmd);
my $fh = "CMD";
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
my $cmd = &get_create_filesystem_command( $disk, $slice, $part, $newfs );
&additional_log( 'exec', undef, $cmd );
my $out = &backquote_command( $cmd . " 2>&1" );
foreach my $line ( split( /\n/, $out ) ) {
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
print &html_escape($line) . "\n";
}
print "</pre>";
if ($?) {
print $text{'newfs_failed'},"<p>\n";
}
my $rc = $? >> 8;
if ($rc) {
print $text{'newfs_failed'}, "<p>\n";
}
else {
print $text{'newfs_done'},"<p>\n";
&webmin_log("newfs", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
}
print $text{'newfs_done'}, "<p>\n";
&webmin_log( "newfs", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object );
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
# Verify filesystem signature if possible
if ( has_command('fstyp') ) {
my $fstyp_out = &backquote_command(
"fstyp " . quote_path( $object->{'device'} ) . " 2>&1" );
$fstyp_out =~ s/[\r\n]+$//;
if ($fstyp_out) {
print "<pre>\n";
print &html_escape($fstyp_out) . "\n";
print "</pre>\n";
}
else {
print
"<b>Warning:</b> fstyp did not detect a filesystem on this device.<p>\n";
}
}
# If a label was provided, set the partition label (GPT slice or BSD sub-partition)
if ( !$in{'label_def'} && defined $in{'label'} && length $in{'label'} ) {
my $errlbl = set_partition_label(
disk => $disk,
slice => $slice,
part => ( $in{'part'} ne '' ? $part : undef ),
label => $in{'label'}
);
if ($errlbl) {
print "Warning: failed to set partition label: \n"
. &html_escape($errlbl) . "\n";
}
}
}
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

View File

@@ -6,56 +6,492 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number") if $in{'slice'};
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my $object;
if ($in{'part'} ne '') {
my ($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
if ( $in{'part'} ne '' ) {
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter");
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
$object = $slice;
}
# If this device is part of a ZFS pool, offer dataset creation instead
my $zdev = get_zfs_device_info($object);
if ($zdev) {
&ui_print_header( $object->{'desc'}, $text{'newfs_title'}, "" );
# Parent pool summary (best effort)
my $parent = $zdev->{'pool'};
my $parent_q = quote_path($parent);
my $zlist = &backquote_command(
"zfs list -H -o name,used,avail,refer,mountpoint $parent_q 2>/dev/null"
);
if ($zlist) {
chomp($zlist);
my @cols = split( /\t/, $zlist );
if ( @cols >= 5 ) {
print "<b>$text{'newfs_zfs_parent'}</b>\n";
print ui_columns_start(
[
$text{'newfs_zfs_fs'}, $text{'newfs_zfs_used'},
$text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'},
$text{'newfs_zfs_mount'}
]
);
print ui_columns_row( [ map { html_escape($_) } @cols[ 0 .. 4 ] ] );
print ui_columns_end();
print "<br/>\n";
}
}
# Existing filesystems under this pool
my $zlist_fs = &backquote_command(
"zfs list -H -r -t filesystem -o name,used,avail,refer,mountpoint $parent_q 2>/dev/null"
);
if ($zlist_fs) {
my @lines = split( /\n/, $zlist_fs );
if ( @lines && $lines[0] =~ /^\Q$parent\E(\t|$)/ ) {
shift @lines;
}
if (@lines) {
print "<b>$text{'newfs_zfs_existing'}</b>\n";
print ui_columns_start(
[
$text{'newfs_zfs_fs'}, $text{'newfs_zfs_used'},
$text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'},
$text{'newfs_zfs_mount'}
]
);
foreach my $ln (@lines) {
my @cols = split( /\t/, $ln );
next unless @cols >= 5;
print ui_columns_row(
[ map { html_escape($_) } @cols[ 0 .. 4 ] ] );
}
print ui_columns_end();
print "<br/>\n";
}
}
# Existing volumes under this pool
my $zlist_vol = &backquote_command(
"zfs list -H -r -t volume -o name,used,avail,refer,volsize $parent_q 2>/dev/null"
);
if ($zlist_vol) {
my @lines = split( /\n/, $zlist_vol );
if ( @lines && $lines[0] =~ /^\Q$parent\E(\t|$)/ ) {
shift @lines;
}
if (@lines) {
print "<b>$text{'newfs_zvol_existing'}</b>\n";
print ui_columns_start(
[
$text{'newfs_zvol_vol'}, $text{'newfs_zfs_used'},
$text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'},
$text{'newfs_zvol_volsize'}
]
);
foreach my $ln (@lines) {
my @cols = split( /\t/, $ln );
next unless @cols >= 5;
print ui_columns_row(
[ map { html_escape($_) } @cols[ 0 .. 4 ] ] );
}
print ui_columns_end();
print "<br/>\n";
}
}
my %fs_descriptions = (
'recordsize' => {
'128K' => '128K (General/Default)',
'1M' => '1M (Media/Large files)',
'4M' => '4M',
'16K' => '16K (Database)',
'4K' => '4K (VM)'
},
'compression' => {
'lz4' => 'lz4 (Recommended)',
'off' => 'off (None)',
'gzip' => 'gzip (High compression)'
},
'atime' => {
'off' => 'off (Performance)',
'on' => 'on (Record access time)'
},
'sync' => {
'disabled' => 'disabled (Performance)',
'standard' => 'standard (Safety)',
'always' => 'always (Maximum Safety)'
},
'acltype' => {
'nfsv4' => 'nfsv4 (ZFS Default)',
'posixacl' => 'posixacl (Linux Default)'
},
'aclinherit' => {
'passthrough' => 'passthrough (SMB Recommended)',
'restricted' => 'restricted (ZFS Default)'
},
'aclmode' => {
'passthrough' => 'passthrough (SMB Recommended)',
'discard' => 'discard (ZFS Default)'
}
);
my @fs_order = (
'recordsize', 'compression', 'atime', 'sync',
'exec', 'canmount', 'acltype', 'aclinherit',
'aclmode', 'xattr'
);
my %fs_defaults = (
'recordsize' => '128K',
'compression' => 'lz4',
'atime' => 'off',
'sync' => 'default',
'acltype' => 'nfsv4',
'aclinherit' => 'passthrough',
'aclmode' => 'passthrough',
'canmount' => 'on',
'exec' => 'on',
'xattr' => 'sa',
);
my %fs_opts = (
'recordsize' =>
'512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K, 256K, 512K, 1M',
'compression' => 'on, off, lz4, gzip',
'atime' => 'on, off',
'sync' => 'standard, always, disabled',
'exec' => 'on, off',
'canmount' => 'on, off, noauto',
'acltype' => 'nfsv4, posixacl',
'aclinherit' =>
'discard, noallow, restricted, passthrough, passthrough-x',
'aclmode' => 'discard, groupmask, passthrough',
'xattr' => 'on, off, sa',
);
my %acl_tooltips = (
'acltype' => $text{'newfs_zfs_acltype_desc'},
'aclinherit' => $text{'newfs_zfs_aclinherit_desc'},
'aclmode' => $text{'newfs_zfs_aclmode_desc'},
);
my $base_url =
"newfs_form.cgi?device="
. $url_device
. "&slice=$url_slice";
$base_url .= "&part=$url_part" if ( $in{'part'} ne '' );
my @tabs = (
[ "zfs", $text{'newfs_zfs_tab_fs'}, $base_url . "&mode=zfs" ],
[ "zvol", $text{'newfs_zfs_tab_vol'}, $base_url . "&mode=zvol" ],
);
print &ui_tabs_start( \@tabs, "mode", $in{'mode'} || $tabs[0]->[0], 1 );
print &ui_tabs_start_tab( "mode", "zfs" );
print &ui_form_start( "zfs_create.cgi", "post", undef,
"onsubmit='return validateFsForm(this)'" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_hidden( "part", $in{'part'} );
print &ui_hidden( "parent", $parent );
print &ui_table_start( $text{'newfs_zfs_header'}, 'width=100%', 2 );
print &ui_table_row( $text{'newfs_zfs_name'},
html_escape($parent) . "/" . &ui_textbox( "zfs", undef, 24 ) );
print &ui_table_row( $text{'newfs_zfs_mountpoint'},
&ui_filebox( 'mountpoint', '', 25, undef, undef, 1 ) . " ("
. $text{'newfs_zfs_mount_blank'}
. ")" );
print &ui_table_end();
print &ui_table_start( $text{'newfs_zfs_opts'}, "width=100%", undef );
foreach my $key (@fs_order) {
my @options;
push( @options, [ 'default', 'default' ] );
foreach my $opt ( split( ", ", $fs_opts{$key} ) ) {
my $label = $fs_descriptions{$key}{$opt} || $opt;
push( @options, [ $opt, $label ] );
}
my $default_val = $fs_defaults{$key} || 'default';
my $help =
$acl_tooltips{$key}
? "<br><small><i>$acl_tooltips{$key}</i></small>"
: "";
my $selected = defined( $in{$key} ) ? $in{$key} : $default_val;
print ui_table_row( $key . ': ',
ui_select( $key, $selected, \@options, 1, 0, 1 ) . $help );
}
my $add_inherit_default =
defined( $in{'add_inherit'} ) ? $in{'add_inherit'} : 1;
print ui_table_row(
$text{'newfs_zfs_aclflags'},
ui_checkbox(
'add_inherit', 1,
$text{'newfs_zfs_aclflags_desc'}, $add_inherit_default
)
);
print &ui_table_end();
print &ui_form_end( [ [ undef, $text{'create'} ] ] );
print &ui_tabs_end_tab( "mode", "zfs" );
# ZVOL tab
print &ui_tabs_start_tab( "mode", "zvol" );
my %zvol_defaults = (
'volblocksize' => '16K',
'compression' => 'lz4',
'sync' => 'default',
'logbias' => 'latency',
'primarycache' => 'all',
'secondarycache' => 'all',
);
my %zvol_opts = (
'volblocksize' => '512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K',
'compression' => 'on, off, lz4, gzip',
'sync' => 'standard, always, disabled',
'logbias' => 'latency, throughput',
'primarycache' => 'all, metadata, none',
'secondarycache' => 'all, metadata, none',
);
my %zvol_desc = (
'volblocksize' => {
'512' => '512B',
'1K' => '1K',
'2K' => '2K',
'4K' => '4K (Swap)',
'8K' => '8K (Databases)',
'16K' => '16K (VM/Default)',
'64K' => '64K (Backups)',
},
'logbias' => {
'latency' => 'latency (databases, NFS sync)',
'throughput' => 'throughput (VM, media/backups)',
},
'primarycache' => {
'all' => 'all (filesystems)',
'metadata' => 'metadata (VM, iSCSI)',
'none' => 'none (Swap)',
},
'secondarycache' => {
'all' => 'all (general filesystems)',
'metadata' => 'metadata (VM, databases)',
'none' => 'none (media)',
},
);
print &ui_form_start( "zvol_create.cgi", "post", undef,
"onsubmit='return validateZvolForm(this)'" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_hidden( "part", $in{'part'} );
print &ui_hidden( "parent", $parent );
print &ui_table_start( $text{'newfs_zvol_header'}, 'width=100%', 2 );
print &ui_table_row( $text{'newfs_zvol_name'},
html_escape($parent) . "/" . &ui_textbox( "zvol", undef, 24 ) );
print &ui_table_row(
$text{'newfs_zvol_size'},
&ui_textbox(
'size', undef, 20, undef, undef, "oninput='updateRefres()'"
)
);
print &ui_table_end();
print &ui_table_start( $text{'newfs_zvol_opts'}, "width=100%", undef );
foreach my $key (
qw(volblocksize compression sync logbias primarycache secondarycache))
{
my @options;
push( @options, [ 'default', 'default' ] );
foreach my $opt ( split( ", ", $zvol_opts{$key} ) ) {
my $label = $zvol_desc{$key}{$opt} || $opt;
push( @options, [ $opt, $label ] );
}
my $default_val = $zvol_defaults{$key} || 'default';
my $selected = defined( $in{$key} ) ? $in{$key} : $default_val;
print ui_table_row( $key . ': ',
ui_select( $key, $selected, \@options, 1, 0, 1 ) );
}
my $sparse_default = defined( $in{'sparse'} ) ? $in{'sparse'} : 1;
print ui_table_row( $text{'newfs_zvol_sparse'},
ui_yesno_radio( 'sparse', $sparse_default )
. " <small>"
. $text{'newfs_zvol_sparse_desc'}
. "</small>" );
print ui_table_row( $text{'newfs_zvol_refreservation'},
ui_textbox( 'refreservation', 'none', 20 )
. "<span id='refres_label'></span>" );
print &ui_table_end();
print &ui_form_end( [ [ undef, $text{'create'} ] ] );
print &ui_tabs_end_tab( "mode", "zvol" );
print &ui_tabs_end(1);
print <<'EOF';
<script type="text/javascript">
function validateFsForm(form) {
var name = form.zfs.value;
var nameRegex = /^[a-zA-Z0-9_\-.:]+$/;
if (!name || !nameRegex.test(name)) {
alert("Invalid Name. Please use alphanumeric characters, -, _, ., or :");
return false;
}
return true;
}
function validateZvolForm(form) {
var name = form.zvol.value;
var nameRegex = /^[a-zA-Z0-9_\-.:]+$/;
if (!name || !nameRegex.test(name)) {
alert("Invalid Name. Please use alphanumeric characters, -, _, ., or :");
return false;
}
if (name.indexOf("/") !== -1 || name.indexOf("@") !== -1 || name.indexOf("#") !== -1) {
alert("Invalid Name. Do not include '/', '@' or '#'.");
return false;
}
if (name.charAt(0) === "-" || name.charAt(0) === ".") {
alert("Invalid Name. It cannot start with '-' or '.'.");
return false;
}
if (name.indexOf("..") !== -1) {
alert("Invalid Name. It cannot contain '..'.");
return false;
}
var size = form.size.value;
var regex = /^\d+(\.\d+)?[KMGTP]?$/i;
if (!size || !regex.test(size) || parseFloat(size) <= 0) {
alert("Invalid size format. Please use format like 10G, 500M, etc.");
return false;
}
var ref = form.refreservation.value;
if (ref && ref.toLowerCase() !== "none") {
if (!regex.test(ref) || parseFloat(ref) <= 0) {
alert("Invalid refreservation format. Please use format like 10G, 500M, etc.");
return false;
}
// Compare sizes when possible
function toBytes(v) {
var m = v.match(/^(\d+(?:\.\d+)?)([KMGTP]?)$/i);
if (!m) return null;
var n = parseFloat(m[1]);
var u = (m[2] || "").toUpperCase();
var mult = 1;
if (u === "K") mult = 1024;
else if (u === "M") mult = 1024*1024;
else if (u === "G") mult = 1024*1024*1024;
else if (u === "T") mult = 1024*1024*1024*1024;
else if (u === "P") mult = 1024*1024*1024*1024*1024;
return n * mult;
}
var sizeB = toBytes(size);
var refB = toBytes(ref);
if (sizeB && refB && refB > sizeB) {
alert("Refreservation cannot be larger than the volume size.");
return false;
}
}
return true;
}
function updateRefres() {
var sparseList = document.getElementsByName("sparse");
var refres = document.getElementsByName("refreservation")[0];
var size = document.getElementsByName("size")[0];
var label = document.getElementById("refres_label");
if (!sparseList || sparseList.length === 0 || !refres || !size || !label) return;
var sparseOn = false;
for (var i = 0; i < sparseList.length; i++) {
if (sparseList[i].checked && sparseList[i].value === "1") {
sparseOn = true;
}
}
if (sparseOn) {
refres.disabled = false;
var sVal = size.value ? size.value : "Size";
label.innerHTML = " (" + sVal + " max, default: none)";
} else {
refres.disabled = true;
label.innerHTML = " (" + "Volume is thick provisioned)";
}
}
function bindSparseRadios() {
var sparseList = document.getElementsByName("sparse");
if (!sparseList) return;
for (var i = 0; i < sparseList.length; i++) {
sparseList[i].onclick = updateRefres;
}
}
window.onload = function() { updateRefres(); bindSparseRadios(); };
</script>
EOF
&ui_print_header($object->{'desc'}, $text{'newfs_title'}, "");
if ( $in{'part'} ne '' ) {
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}
exit;
}
print &ui_form_start("newfs.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_hidden("slice", $in{'slice'});
print &ui_hidden("part", $in{'part'});
print &ui_table_start($text{'newfs_header'}, undef, 2);
# Default: UFS newfs form
&ui_print_header( $object->{'desc'}, $text{'newfs_title'}, "" );
print &ui_table_row($text{'part_device'},
"<tt>$object->{'device'}</tt>");
my $confirm_msg = $text{'confirm_overwrite'}
|| 'You will destroy/overwrite existing data structures. Continue?';
my $confirm_js = $confirm_msg;
$confirm_js =~ s/\\/\\\\/g;
$confirm_js =~ s/'/\\'/g;
$confirm_js =~ s/\r?\n/\\n/g;
print &ui_form_start( "newfs.cgi", "post", undef,
"onsubmit=\"return confirm('$confirm_js')\"" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_hidden( "part", $in{'part'} );
print &ui_table_start( $text{'newfs_header'}, undef, 2 );
print &ui_table_row($text{'newfs_free'},
&ui_opt_textbox("free", undef, 4, $text{'newfs_deffree'})."%");
print &ui_table_row( $text{'part_device'}, "<tt>$object->{'device'}</tt>" );
print &ui_table_row($text{'newfs_trim'},
&ui_yesno_radio("trim", 0));
print &ui_table_row( $text{'newfs_free'},
&ui_opt_textbox( "free", undef, 4, $text{'newfs_deffree'} ) . "%" );
print &ui_table_row($text{'newfs_label'},
&ui_opt_textbox("label", undef, 20, $text{'newfs_none'}));
print &ui_table_row( $text{'newfs_trim'}, &ui_yesno_radio( "trim", 0 ) );
print &ui_table_row( $text{'newfs_label'},
&ui_opt_textbox( "label", undef, 20, $text{'newfs_none'} ) );
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'newfs_create'} ] ]);
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
if ( $in{'part'} ne '' ) {
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

View File

@@ -6,54 +6,109 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
&ui_print_header($slice->{'desc'}, $text{'npart_title'}, "");
&ui_print_header( $slice->{'desc'}, $text{'npart_title'}, "" );
print &ui_form_start("create_part.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_hidden("slice", $in{'slice'});
print &ui_table_start($text{'npart_header'}, undef, 2);
print &ui_form_start( "create_part.cgi", "post" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_table_start( $text{'npart_header'}, undef, 2 );
# Partition number (first free)
my %used = map { $_->{'letter'}, $_ } @{$slice->{'parts'}};
# Partition number (first free, skipping 'c' which is reserved for the whole slice)
my %used = map { $_->{'letter'}, $_ } @{ $slice->{'parts'} };
$used{'c'} = 1; # Reserve 'c' for the whole slice (BSD convention)
my $l = 'a';
while($used{$l}) {
$l++;
}
print &ui_table_row($text{'npart_letter'},
&ui_textbox("letter", $l, 4));
while ( $used{$l} ) {
$l++;
}
print &ui_table_row( $text{'npart_letter'},
&ui_textbox( "letter", $l, 4 ) . " <i>("
. $text{'npart_creserved'}
. ")</i>" );
# Slice size in blocks
print &ui_table_row($text{'npart_diskblocks'},
$slice->{'blocks'});
print &ui_table_row( $text{'npart_diskblocks'}, $slice->{'blocks'} );
# Start and end blocks (defaults to last part)
my ($start, $end) = (0, $slice->{'blocks'});
foreach my $p (sort { $a->{'startblock'} cmp $b->{'startblock'} }
@{$slice->{'parts'}}) {
$start = $p->{'startblock'} + $p->{'blocks'} + 1;
}
print &ui_table_row($text{'nslice_start'},
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
# Start and end blocks for BSD partitions are SLICE-RELATIVE (not disk-absolute)
# Start at 0 (or after last partition), end at slice size - 1
my ( $start, $end ) = ( 0, $slice->{'blocks'} - 1 );
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $slice->{'parts'} } )
{
# Partitions are already stored as slice-relative
$start = $p->{'startblock'} + $p->{'blocks'};
}
if ( defined $in{'start'} && $in{'start'} =~ /^\d+$/ ) {
$start = $in{'start'};
}
if ( defined $in{'end'} && $in{'end'} =~ /^\d+$/ ) { $end = $in{'end'}; }
print &ui_table_row( $text{'nslice_start'} . " " . $text{'npart_slicerel'},
&ui_textbox( "start", $start, 10 ) );
print &ui_table_row( $text{'nslice_end'} . " " . $text{'npart_slicerel'},
&ui_textbox( "end", $end, 10 ) );
# Partition type
print &ui_table_row($text{'npart_type'},
&ui_select("type", '4.2BSD',
[ &list_partition_types() ]));
# For BSD-on-MBR inner label partitions, offer FreeBSD partition types
my $scheme = 'BSD';
my $default_ptype = 'freebsd-ufs';
print &ui_table_row( $text{'npart_type'},
&ui_select( "type", $default_ptype, [ list_partition_types($scheme) ] ) );
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'create'} ] ]);
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
# Existing partitions summary
if ( @{ $slice->{'parts'} || [] } ) {
my $zfs = get_all_zfs_info();
print &ui_hr();
print &ui_columns_start(
[
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'},
$text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'},
$text{'slice_role'}
],
$text{'epart_existing'}
);
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $slice->{'parts'} } )
{
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
my @stp = fdisk::device_status( $p->{'device'} );
my $usep =
$zfs->{ $p->{'device'} }
|| fdisk::device_status_link(@stp)
|| $text{'part_nouse'};
my $rolep = get_partition_role($p);
my $pb = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
print &ui_columns_row(
[
uc( $p->{'letter'} ),
&html_escape($ptype),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
( $pb ? safe_nice_size($pb) : '-' ),
&html_escape($usep),
&html_escape($rolep)
]
);
}
print &ui_columns_end();
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -6,30 +6,39 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'part_err'});
&error_setup( $text{'part_err'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
# Check if in use
my @st = &fdisk::device_status($part->{'device'});
my @st = &fdisk::device_status( $part->{'device'} );
my $use = &fdisk::device_status_link(@st);
if (@st && $st[2]) {
&error(&text('part_esave', $use));
}
if ( @st && $st[2] ) {
&error( &text( 'part_esave', &html_escape($use) ) );
}
# Make the change
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
or &error( $text{'part_etype'} || 'Invalid partition type' );
$part->{'type'} = $in{'type'};
my $err = &save_partition($disk, $slice, $part);
&error($err) if ($err);
my $err = &save_partition( $disk, $slice, $part );
&error( &html_escape($err) ) if ($err);
&webmin_log("modify", "part", $part->{'device'}, $part);
&redirect("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}");
&webmin_log( "modify", "part", $part->{'device'}, $part );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&redirect("edit_slice.cgi?device=$url_device&slice=$url_slice");

View File

@@ -6,25 +6,53 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'slice_err'});
&error_setup( $text{'slice_err'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
# Apply changes
my $oldslice = { %$slice };
$slice->{'type'} = $in{'type'};
if (!$slice->{'active'}) {
$slice->{'active'} = $in{'active'};
}
my $err = &modify_slice($disk, $oldslice, $slice);
&error($err) if ($err);
my $oldslice = {%$slice};
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
or &error( $text{'nslice_etype'} || 'Invalid slice type' );
$slice->{'type'} = $in{'type'};
$slice->{'active'} = $in{'active'} if ( defined $in{'active'} );
&webmin_log("modify", "slice", $slice->{'device'}, $slice);
&redirect("edit_disk.cgi?device=$in{'device'}");
# Apply active flag for MBR disks via gpart set/unset when it changed
my $base = $disk->{'device'};
$base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ( is_using_gpart() && $ds && $ds->{'scheme'} && $ds->{'scheme'} !~ /GPT/i ) {
my $idx = _safe_uint( slice_number($slice) );
&error( $text{'slice_egone'} ) unless defined $idx;
if ( defined $oldslice->{'active'}
&& defined $slice->{'active'}
&& $oldslice->{'active'} != $slice->{'active'} )
{
my $cmd =
$slice->{'active'}
? "gpart set -a active -i $idx " . quote_path($base)
: "gpart unset -a active -i $idx " . quote_path($base);
my $out = backquote_command("$cmd 2>&1");
if ( $? != 0 ) {
&error( "Failed to change active flag: " . &html_escape($out) );
}
}
}
my $err = &modify_slice( $disk, $oldslice, $slice );
&error( &html_escape($err) ) if ($err);
&webmin_log( "modify", "slice", $slice->{'device'}, $slice );
my $url_device = &urlize( $in{'device'} );
&redirect("edit_disk.cgi?device=$url_device");

View File

@@ -0,0 +1,49 @@
#!/usr/local/bin/perl
# Save slice label (GPT label or glabel)
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
&error_setup( $text{'slice_label_err'} );
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
my $label = defined $in{'label'} ? $in{'label'} : '';
$label =~ s/^\s+|\s+$//g;
$label ne '' || &error( $text{'slice_label_empty'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
# Ensure we can set labels on this scheme
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $ds = get_disk_structure($base_device);
if ( !$ds || !$ds->{'scheme'} || $ds->{'scheme'} !~ /GPT/i ) {
if ( !has_command('glabel') ) {
&error( $text{'slice_label_noglabel'} );
}
}
my $err = set_partition_label(
disk => $disk,
slice => $slice,
label => $label,
);
&error( &html_escape($err) ) if ($err);
&webmin_log( "label", "slice", $slice->{'device'}, { 'label' => $label } );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&redirect("edit_slice.cgi?device=$url_device&slice=$url_slice");

View File

@@ -6,57 +6,177 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
$disk || &error( $text{'disk_egone'} );
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
my $url_device = &urlize( $in{'device'} );
print &ui_form_start("create_slice.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_table_start($text{'nslice_header'}, undef, 2);
&ui_print_header( $disk->{'desc'}, $text{'nslice_title'}, "" );
# Determine scheme for read-only behavior and note
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
my $is_gpt =
( is_using_gpart()
&& $disk_structure
&& ( $disk_structure->{'scheme'} || '' ) =~ /GPT/i );
# Check if there is any free space on the device
my $has_free_space = 0;
if ( $disk_structure && $disk_structure->{'entries'} ) {
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
if ( $entry->{'type'} eq 'free' && $entry->{'size'} > 0 ) {
$has_free_space = 1;
last;
}
}
}
# If no free space, show error and return
if ( !$has_free_space ) {
print "<p><b>$text{'nslice_enospace'}</b></p>\n";
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );
exit;
}
print &ui_form_start( "create_slice.cgi", "post" );
print &ui_hidden( "device", $in{'device'} );
print &ui_table_start( $text{'nslice_header'}, undef, 2 );
# Slice number (first free)
my %used = map { $_->{'number'}, $_ } @{$disk->{'slices'}};
my $n = 1;
while($used{$n}) {
$n++;
}
print &ui_table_row($text{'nslice_number'},
&ui_textbox("number", $n, 6));
my %used = map { $_->{'number'}, $_ } @{ $disk->{'slices'} };
my $n = 1;
while ( $used{$n} ) {
$n++;
}
my $num_field =
$is_gpt
? "<input type='text' name='number' value='"
. &html_escape($n)
. "' size='6' readonly> <span style='color:#666;font-style:italic'>"
. $text{'nslice_autonext'}
. "</span>"
: &ui_textbox( "number", $n, 6 );
print &ui_table_row( $text{'nslice_number'}, $num_field );
# Disk size in blocks
print &ui_table_row($text{'nslice_diskblocks'},
$disk->{'blocks'});
# Disk size in blocks (prefer GPART total blocks)
my $disk_blocks =
( $disk_structure && $disk_structure->{'total_blocks'} )
? $disk_structure->{'total_blocks'}
: ( $disk->{'blocks'} || 0 );
print &ui_table_row( $text{'nslice_diskblocks'}, $disk_blocks );
# Start and end blocks (defaults to last slice+1)
my ($start, $end) = (63, $disk->{'blocks'});
foreach my $s (sort { $a->{'startblock'} cmp $b->{'startblock'} }
@{$disk->{'slices'}}) {
$start = $s->{'startblock'} + $s->{'blocks'} + 1;
}
print &ui_table_row($text{'nslice_start'},
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
# Start and end blocks (defaults to last slice+1). Allow prefill from query.
my ( $start, $end ) = ( 2048, $disk_blocks > 0 ? $disk_blocks - 1 : 0 );
foreach my $s ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $disk->{'slices'} } )
{
$start = $s->{'startblock'} + $s->{'blocks'}; # leave 1 block (512B) gap
}
if ( defined $in{'start'} && $in{'start'} =~ /^\d+$/ ) {
$start = $in{'start'};
}
if ( defined $in{'end'} && $in{'end'} =~ /^\d+$/ ) { $end = $in{'end'}; }
print &ui_table_row( $text{'nslice_start'},
&ui_textbox( "start", $start, 10 ) );
print &ui_table_row( $text{'nslice_end'}, &ui_textbox( "end", $end, 10 ) );
# Slice type
print &ui_table_row($text{'nslice_type'},
&ui_select("type", 'a5',
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
if ( is_using_gpart() ) {
my $scheme =
( $disk_structure && $disk_structure->{'scheme'} )
? $disk_structure->{'scheme'}
: 'GPT';
my $default_stype = ( $scheme =~ /GPT/i ) ? 'freebsd-zfs' : 'freebsd';
print &ui_table_row( $text{'nslice_type'},
&ui_select( "type", $default_stype, [ list_partition_types($scheme) ] )
);
}
else {
print &ui_table_row(
$text{'nslice_type'},
&ui_select(
"type", 'a5',
[
sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] } &fdisk::list_tags()
]
)
);
}
# Also create partition?
print &ui_table_row($text{'nslice_makepart'},
&ui_yesno_radio("makepart", 1));
# Also create partition? (only for MBR slices with BSD disklabel support)
if ( !$is_gpt ) {
print &ui_table_row( $text{'slice_add'}, &ui_yesno_radio( "makepart", 1 ) );
}
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'create'} ] ]);
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# Existing slices summary
print &ui_hr();
print &ui_subheading( $text{'nslice_existing_slices_label'} );
print &ui_columns_start(
[
$text{'disk_no'}, $text{'disk_type'}, $text{'disk_start'},
$text{'disk_end'}, $text{'disk_size'}
],
$text{'nslice_existing_header'}
);
foreach
my $s ( sort { $a->{'number'} <=> $b->{'number'} } @{ $disk->{'slices'} } )
{
my $stype = get_type_description( $s->{'type'} ) || $s->{'type'};
my $szb = bytes_from_blocks( $s->{'device'}, $s->{'blocks'} );
my $sz = defined $szb ? safe_nice_size($szb) : '-';
print &ui_columns_row(
[
$s->{'number'}, &html_escape($stype),
$s->{'startblock'}, $s->{'startblock'} + $s->{'blocks'} - 1,
$sz,
]
);
}
print &ui_columns_end();
# Existing partitions summary
my @parts_rows;
foreach
my $s ( sort { $a->{'number'} <=> $b->{'number'} } @{ $disk->{'slices'} } )
{
next unless @{ $s->{'parts'} || [] };
foreach my $p ( @{ $s->{'parts'} } ) {
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
my $pb = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
my $psz = defined $pb ? safe_nice_size($pb) : '-';
push @parts_rows,
[
$s->{'number'}, uc( $p->{'letter'} ),
&html_escape($ptype), $p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1, $psz
];
}
}
if (@parts_rows) {
print &ui_subheading( $text{'nslice_existing_parts_label'} );
print &ui_columns_start(
[
'Slice', $text{'slice_letter'}, $text{'slice_type'},
$text{'slice_start'}, $text{'slice_end'}, $text{'slice_size'}
],
$text{'nslice_existing_parts'}
);
foreach my $row (@parts_rows) { print &ui_columns_row($row); }
print &ui_columns_end();
}
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

110
bsdfdisk/smart.cgi Executable file
View File

@@ -0,0 +1,110 @@
#!/usr/local/bin/perl
# Show SMART status for a given device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
# Validate device param
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
# Check smartctl availability
&has_command('smartctl')
or &error( $text{'index_ecmd'}
? &text( 'index_ecmd', 'smartctl' )
: 'smartctl not available' );
my $device = $in{'device'};
my $dev_html = &html_escape($device);
&ui_print_header( $dev_html, $text{'disk_smart'} || 'SMART Status', "" );
print "<div class='panel panel-default'>\n";
print "<div class='panel-heading'><h3 class='panel-title'>SMART status for "
. "<tt>$dev_html</tt></h3></div>\n";
print "<div class='panel-body'>\n";
sub _smartctl_needs_type {
my ($out) = @_;
return ( $out =~ /Please specify device type with the -d option/i
|| $out =~ /Unknown USB bridge/i );
}
sub _smartctl_no_smart {
my ($out) = @_;
return ( $out =~ /Read Device Identity failed/i
|| $out =~ /unsupported scsi opcode/i
|| $out =~ /Device does not support SMART/i
|| $out =~ /SMART support is:\s*Unavailable/i
|| $out =~ /mandatory SMART command failed/i );
}
my $cmd = "smartctl -a " . &quote_path($device) . " 2>&1";
my $out = &backquote_command($cmd);
my $used_cmd = $cmd;
my $note;
# If smartctl requests a device type or indicates SMART unsupported,
# try common USB bridge options.
if ( _smartctl_needs_type($out) || _smartctl_no_smart($out) ) {
my @types = (
'sat,auto', 'sat', 'scsi', 'auto',
'usbjmicron', 'usbprolific', 'usbcypress', 'usbsunplus',
);
my $best_out = $out;
my $best_cmd = $cmd;
foreach my $t (@types) {
my $try_cmd = "smartctl -a -d $t " . &quote_path($device) . " 2>&1";
my $try_out = &backquote_command($try_cmd);
# If it still needs a type, keep trying
if ( _smartctl_needs_type($try_out) ) {
$best_out = $try_out;
$best_cmd = $try_cmd;
next;
}
# If SMART commands fail, try permissive once
if ( _smartctl_no_smart($try_out) ) {
my $perm_cmd = "smartctl -a -T permissive -d $t "
. &quote_path($device) . " 2>&1";
my $perm_out = &backquote_command($perm_cmd);
if ( !_smartctl_needs_type($perm_out)
&& !_smartctl_no_smart($perm_out) )
{
$out = $perm_out;
$used_cmd = $perm_cmd;
last;
}
$best_out = $perm_out;
$best_cmd = $perm_cmd;
next;
}
# Success
$out = $try_out;
$used_cmd = $try_cmd;
last;
}
if ( $used_cmd eq $cmd ) {
$out = $best_out;
$used_cmd = $best_cmd;
if ( _smartctl_no_smart($out) ) {
$note = "SMART may not be supported by this USB device or bridge.";
}
}
}
if ($note) {
print "<div class='alert alert-warning'>$note</div>\n";
}
print "<pre>" . &html_escape("Command: $used_cmd\n\n$out") . "</pre>\n";
print "</div></div>\n";
&ui_print_footer( "edit_disk.cgi?device=" . &urlize($device),
$text{'disk_return'} );

121
bsdfdisk/zfs_create.cgi Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/local/bin/perl
# Create a ZFS filesystem (dataset) within the pool owning this device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
&error_setup( $text{'newfs_zfs_err'} );
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
&has_command('zfs') or &error( $text{'newfs_zfs_nozfs'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
}
# Determine ZFS pool for this device
my $zdev = get_zfs_device_info($object);
$zdev || &error( $text{'newfs_zfs_notinpool'} );
my $parent = $zdev->{'pool'};
# Validate dataset name
my $name = $in{'zfs'};
$name =~ s/^\s+|\s+$//g if defined $name;
$name && $name =~ /^[a-zA-Z0-9_\-.:]+$/ or &error( $text{'newfs_zfs_badname'} );
my $dataset = $parent . "/" . $name;
# Allowed property values
my %allowed = (
'recordsize' =>
{ map { $_ => 1 } qw(512 1K 2K 4K 8K 16K 32K 64K 128K 256K 512K 1M) },
'compression' => { map { $_ => 1 } qw(on off lz4 gzip) },
'atime' => { map { $_ => 1 } qw(on off) },
'sync' => { map { $_ => 1 } qw(standard always disabled) },
'exec' => { map { $_ => 1 } qw(on off) },
'canmount' => { map { $_ => 1 } qw(on off noauto) },
'acltype' => { map { $_ => 1 } qw(nfsv4 posixacl) },
'aclinherit' => {
map { $_ => 1 }
qw(discard noallow restricted passthrough passthrough-x)
},
'aclmode' => { map { $_ => 1 } qw(discard groupmask passthrough) },
'xattr' => { map { $_ => 1 } qw(on off sa) },
);
my @props =
qw(recordsize compression atime sync exec canmount acltype aclinherit aclmode xattr);
my @opts;
foreach my $p (@props) {
next if ( !defined $in{$p} || $in{$p} eq '' || $in{$p} eq 'default' );
$allowed{$p} && $allowed{$p}->{ $in{$p} }
or &error( &text( 'newfs_zfs_badopt', $p ) );
push( @opts, "-o $p=" . &quote_path( $in{$p} ) );
}
if ( defined $in{'mountpoint'} && $in{'mountpoint'} ne '' ) {
push( @opts, "-o mountpoint=" . &quote_path( $in{'mountpoint'} ) );
}
&ui_print_unbuffered_header( $object->{'desc'}, $text{'newfs_zfs_title'}, "" );
print &text( 'newfs_zfs_creating', "<tt>" . &html_escape($dataset) . "</tt>" ),
"<br>\n";
print "<pre>\n";
my $cmd = "zfs create " . join( " ", @opts ) . " " . &quote_path($dataset);
&additional_log( 'exec', undef, $cmd );
my $fh;
&open_execute_command( $fh, $cmd, 2 );
if ($fh) {
while (<$fh>) { print &html_escape($_); }
close($fh);
}
print "</pre>";
if ($?) {
print $text{'newfs_zfs_failed'}, "<p>\n";
}
else {
# Optional ACL inherit flags
if ( $in{'add_inherit'} ) {
my $cmd2 = acl_inherit_flags_cmd($dataset);
if ($cmd2) { system($cmd2); }
}
print $text{'newfs_zfs_done'}, "<p>\n";
&webmin_log( "zfs-create", "dataset", $dataset, { parent => $parent } );
}
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

128
bsdfdisk/zvol_create.cgi Normal file
View File

@@ -0,0 +1,128 @@
#!/usr/local/bin/perl
# Create a ZFS volume (zvol) within the pool owning this device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
&error_setup( $text{'newfs_zvol_err'} );
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
&has_command('zfs') or &error( $text{'newfs_zfs_nozfs'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
}
# Determine ZFS pool for this device
my $zdev = get_zfs_device_info($object);
$zdev || &error( $text{'newfs_zfs_notinpool'} );
my $parent = $zdev->{'pool'};
# Validate zvol name and size
my $name = $in{'zvol'};
$name =~ s/^\s+|\s+$//g if defined $name;
$name && $name =~ /^[a-zA-Z0-9_\-.:]+$/
or &error( $text{'newfs_zvol_badname'} );
my $size = $in{'size'};
$size =~ s/^\s+|\s+$//g if defined $size;
$size && $size =~ /^\d+(\.\d+)?[KMGTP]?$/i
or &error( $text{'newfs_zvol_badsize'} );
my $dataset = $parent . "/" . $name;
# Allowed property values
my %allowed = (
'volblocksize' => { map { $_ => 1 } qw(512 1K 2K 4K 8K 16K 32K 64K 128K) },
'compression' => { map { $_ => 1 } qw(on off lz4 gzip) },
'sync' => { map { $_ => 1 } qw(standard always disabled) },
'logbias' => { map { $_ => 1 } qw(latency throughput) },
'primarycache' => { map { $_ => 1 } qw(all metadata none) },
'secondarycache' => { map { $_ => 1 } qw(all metadata none) },
);
my @props =
qw(volblocksize compression sync logbias primarycache secondarycache);
my @opts;
foreach my $p (@props) {
next if ( !defined $in{$p} || $in{$p} eq '' || $in{$p} eq 'default' );
$allowed{$p} && $allowed{$p}->{ $in{$p} }
or &error( &text( 'newfs_zvol_badopt', $p ) );
push( @opts, "-o $p=" . &quote_path( $in{$p} ) );
}
my $sparse = $in{'sparse'};
my $refres = $in{'refreservation'};
if ( defined $refres ) {
$refres =~ s/^\s+|\s+$//g;
if ( $refres ne '' && lc($refres) ne 'none' ) {
$refres =~ /^\d+(\.\d+)?[KMGTP]?$/i
or &error( $text{'newfs_zvol_badrefres'} );
push( @opts, "-o refreservation=" . &quote_path($refres) );
}
}
&ui_print_unbuffered_header( $object->{'desc'}, $text{'newfs_zvol_title'}, "" );
print &text( 'newfs_zvol_creating', "<tt>" . &html_escape($dataset) . "</tt>" ),
"<br>\n";
print "<pre>\n";
my $cmd =
"zfs create "
. ( $sparse ? "-s " : "" ) . "-V "
. &quote_path($size) . " "
. join( " ", @opts ) . " "
. &quote_path($dataset);
&additional_log( 'exec', undef, $cmd );
my $fh;
&open_execute_command( $fh, $cmd, 2 );
if ($fh) {
while (<$fh>) { print &html_escape($_); }
close($fh);
}
print "</pre>";
if ($?) {
print $text{'newfs_zvol_failed'}, "<p>\n";
}
else {
print $text{'newfs_zvol_done'}, "<p>\n";
&webmin_log( "zfs-create", "zvol", $dataset,
{ parent => $parent, volsize => $size } );
}
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

View File

@@ -10,6 +10,24 @@ use WebminCore;
$parallel_max = 20;
%access = &get_module_acl();
# validate_remote_download_target()
# Validates URL-derived download target fields in %in
sub validate_remote_download_target
{
$in{'host'} =~ /^\S+$/ || &error("Invalid download host");
my @ips = &to_ipaddress($in{'host'});
push(@ips, &to_ip6address($in{'host'}));
@ips || &error("Invalid download host");
if ($in{'ftpfile'}) {
$in{'ftpfile'} =~ /^\/\S+$/ || &error("Invalid FTP path");
}
else {
$in{'port'} =~ /^\d+$/ && $in{'port'} >= 1 && $in{'port'} <= 65535 ||
&error("Invalid HTTP port");
$in{'page'} =~ /^\/\S*$/ || &error("Invalid HTTP path");
}
}
# list_software_hosts()
# Returns a list of all hosts whose software is being managed by this module
sub list_software_hosts

View File

@@ -24,6 +24,9 @@ else {
}
$in{'source'} == 3 || -r $in{'file'} || &error($text{'do_edeleted'});
if ($in{'source'} == 2 && $in{'down'}) {
&validate_remote_download_target();
}
&ui_print_header(undef, $text{'do_title'}, "");
# Setup error handler for down hosts
@@ -75,8 +78,9 @@ foreach $h (@hosts) {
elsif ($in{'source'} == 0) {
# Is the file the same on remote (like if we have NFS)
local @st = stat($in{'file'});
local $qfile = &quote_literal_escape($in{'file'});
local $rst = &remote_eval($s->{'host'}, "software",
"[ stat('$in{'file'}') ]");
"[ stat('$qfile') ]");
local @rst = @$rst;
if (@st && @rst && $st[7] == $rst[7] &&
$st[9] == $rst[9]) {
@@ -170,8 +174,11 @@ foreach $h (@hosts) {
}
}
}
&remote_eval($s->{'host'}, "software", "unlink('$rfile')")
if ($need_unlink);
if ($need_unlink) {
local $qfile = &quote_literal_escape($rfile);
&remote_eval($s->{'host'}, "software",
"unlink('$qfile')");
}
print $wh &serialise_variable(\@rv);
close($wh);

View File

@@ -12,6 +12,9 @@ foreach $p (@packages) {
push(@names, $n); push(@descs, $d);
}
-r $in{'file'} || &error($text{'do_edeleted'});
if ($in{'source'} == 2 && $in{'down'}) {
&validate_remote_download_target();
}
&ui_print_header(undef, $text{'do_title'}, "");
print "<b>",&text('do_header', join(" ", @names)),"</b><p>\n";
@@ -78,7 +81,10 @@ foreach $h (@hosts) {
}
}
}
&remote_eval($s->{'host'}, "software", "unlink('$rfile')") if ($need_unlink);
if ($need_unlink) {
local $qfile = &quote_literal_escape($rfile);
&remote_eval($s->{'host'}, "software", "unlink('$qfile')");
}
}
unlink($in{'file'}) if ($in{'need_unlink'});
print "<p><b>$text{'do_done'}</b><p>\n";

View File

@@ -332,7 +332,8 @@ foreach $h (@hosts) {
\$ENV{'config_dir'} = \$config{'usermin_dir'};
\$ENV{'webmin_upgrade'} = 1;
\$ENV{'autothird'} = 1;
\$out = `(cd $extract/usermin-$version && $setup) </dev/null 2>&1 | tee /tmp/.webmin/usermin-setup.out`;
\$tmp = &tempname_dir();
\$out = `(cd $extract/usermin-$version && $setup) </dev/null 2>&1 | tee \$tmp/usermin-setup.out`;
(\$out, \$?)");
if ($out !~ /success/i) {
print $wh &serialise_variable(

View File

@@ -423,7 +423,8 @@ foreach $h (@hosts) {
\$ENV{'config_dir'} = \$config_directory;
\$ENV{'webmin_upgrade'} = 1;
\$ENV{'autothird'} = 1;
\$out = `(cd $extract/webmin-$version && $setup) </dev/null 2>&1 | tee /tmp/.webmin/webmin-setup.out`;
\$tmp = &tempname_dir();
\$out = `(cd $extract/webmin-$version && $setup) </dev/null 2>&1 | tee \$tmp/webmin-setup.out`;
(\$out, \$?)");
if ($ex || $out !~ /success|^0$/i) {
print $wh &serialise_variable(

View File

@@ -4,7 +4,7 @@
require './cpan-lib.pl';
&ReadParse();
$tmp_base = $gconfig{'tempdir'} || "/tmp/.webmin";
$tmp_base = $gconfig{'tempdir'} || &default_webmin_temp_dir();
foreach $f (split(/\0/, $in{'file'})) {
$f =~ /^\Q$tmp_base\E\// || &error($text{'delete_efile'});
unlink($f);

View File

@@ -43,7 +43,9 @@ if (!$fcron) {
local $user;
opendir(DIR, $config{'cron_dir'});
while($user = readdir(DIR)) {
system("cp $config{'cron_dir'}/$user $cron_temp_file");
next if ($user =~ /^\./);
system("cp ".quotemeta("$config{'cron_dir'}/$user").
" ".quotemeta($cron_temp_file));
&copy_crontab($user);
}
closedir(DIR);

View File

@@ -309,7 +309,8 @@ elsif ($fcron) {
undef, $cron_temp_file, undef);
}
else {
system("cp ".&translate_filename("$config{'cron_dir'}/$_[0]->{'user'}").
system("cp ".quotemeta(
&translate_filename("$config{'cron_dir'}/$_[0]->{'user'}")).
" ".quotemeta($cron_temp_file)." 2>/dev/null");
}
}
@@ -513,7 +514,7 @@ if (&read_file_contents($cron_temp_file) =~ /\S/) {
$ENV{"VISUAL"} = $ENV{"EDITOR"} =
"$module_root_directory/cron_editor.pl";
$ENV{"CRON_EDITOR_COPY"} = $cron_temp_file;
system("chown $_[0] $cron_temp_file");
system("chown ".quotemeta($_[0])." ".quotemeta($cron_temp_file));
local $oldpwd = &get_current_dir();
chdir("/");
if ($single_user) {
@@ -600,7 +601,7 @@ sub user_sub
{
local($tmp);
$tmp = $_[0];
$tmp =~ s/USER/$_[1]/g;
$tmp =~ s/USER/quotemeta($_[1])/ge;
return $tmp;
}
@@ -1557,7 +1558,7 @@ if ($err) {
=head2 cleanup_temp_files
Called from cron to delete old files in the Webmin /tmp directory, and also
Called from cron to delete old files in the Webmin temp directory, and also
old lock links directories.
=cut
@@ -1569,7 +1570,7 @@ if (!$gconfig{'tempdelete_days'}) {
return;
}
# Cleanup files in /tmp/.webmin
# Cleanup files in the default Webmin temp directory
if ($gconfig{'tempdir'} && !$gconfig{'tempdirdelete'}) {
print STDERR "Temp file clearing is not done for the custom directory $gconfig{'tempdir'}\n";
}
@@ -1577,7 +1578,7 @@ else {
my $tempdir = &transname();
$tempdir =~ s/\/([^\/]+)$//;
if (!$tempdir || $tempdir eq "/") {
$tempdir = "/tmp/.webmin";
$tempdir = &default_webmin_temp_dir();
}
my $cutoff = time() - $gconfig{'tempdelete_days'}*24*60*60;

View File

@@ -5,8 +5,8 @@
sleep(1); # This is needed because the stupid crontab -e command
# checks the mtime before and after editing, and if they are
# the same it assumes no change has been made!!
open(SRC, "<".$ENV{"CRON_EDITOR_COPY"});
open(DST, ">".$ARGV[0]) || die "Failed to open $ARGV[0] : $!";
open(SRC, "<", $ENV{"CRON_EDITOR_COPY"});
open(DST, ">", $ARGV[0]) || die "Failed to open $ARGV[0] : $!";
while(<SRC>) {
if (!/^#.*DO NOT EDIT/i && !/^#.*installed on/i &&
!/^#.*Cron version/i) {

View File

@@ -57,7 +57,7 @@ if ($in{'bg'}) {
&open_tempfile(TEMP, ">$temp");
&print_tempfile(TEMP, $input);
&close_tempfile(TEMP);
&execute_command("(($lines[0]) ; rm -f $temp) &", $temp,
&execute_command("(($lines[0]) ; rm -f ".quotemeta($temp).") &", $temp,
undef, undef);
}
else {

View File

@@ -10,6 +10,10 @@ use POSIX;
use Socket;
$force_lang = $default_lang;
&init_config();
my $DEFAULT_RPC_TIMEOUT = 60;
my $DEFAULT_RPC_IDLE_FACTOR = 6;
print "Content-type: text/plain\n\n";
# Can this user make remote calls?
@@ -34,7 +38,7 @@ if ($aerr) {
exit;
}
if (open(RANDOM, "/dev/urandom")) {
local $tmpsid;
my $tmpsid;
read(RANDOM, $tmpsid, 16);
$sid = lc(unpack('h*', $tmpsid));
close RANDOM;
@@ -45,6 +49,16 @@ else {
$version = &get_webmin_version();
print "1 $port $sid $version\n";
# Timeout
my $rpc_idle_factor = $gconfig{'rpc_idle_factor'} || $DEFAULT_RPC_IDLE_FACTOR;
if ($rpc_idle_factor !~ /^\d+$/ || $rpc_idle_factor < 1) {
$rpc_idle_factor = $DEFAULT_RPC_IDLE_FACTOR;
}
my $config_rpc_timeout = $gconfig{'rpc_timeout'} || $DEFAULT_RPC_TIMEOUT;
if ($config_rpc_timeout !~ /^\d+$/ || $config_rpc_timeout < 1) {
$config_rpc_timeout = $DEFAULT_RPC_TIMEOUT;
}
# Fork and listen for calls ..
$pid = fork();
if ($pid < 0) {
@@ -57,14 +71,14 @@ untie(*STDIN);
untie(*STDOUT);
# Accept the TCP connection
local $rmask;
my $rmask;
vec($rmask, fileno(MAIN), 1) = 1;
if ($use_ipv6) {
vec($rmask, fileno(MAIN6), 1) = 1;
}
$sel = select($rmask, undef, undef, 60);
$sel = select($rmask, undef, undef, $config_rpc_timeout);
if ($sel <= 0) {
print STDERR "fastrpc: accept timed out\n"
print STDERR "fastrpc[$$]: accept timed out\n"
if ($gconfig{'rpcdebug'});
exit;
}
@@ -78,63 +92,83 @@ else {
die "No connection on any socket!";
}
die "accept failed : $!" if (!$acptaddr);
$oldsel = select(SOCK);
my $oldsel = select(SOCK);
$| = 1;
select($oldsel);
$rcount = 0;
my $rcount = 0;
my %xfer_kids;
while(1) {
# Clean up the list of waiting sub-processes
foreach my $p (keys %xfer_kids) {
my $waited_pid = waitpid($p, POSIX::WNOHANG());
delete($xfer_kids{$p}) if ($waited_pid > 0 || $waited_pid == -1);
}
# Wait for the request. Wait longer if this isn't the first one
local $rmask;
my $rmask;
vec($rmask, fileno(SOCK), 1) = 1;
local $sel = select($rmask, undef, undef, $rcount ? 360 : 60);
my $timeout = $rcount
? $config_rpc_timeout * $rpc_idle_factor
: $config_rpc_timeout;
my $sel = select($rmask, undef, undef, $timeout);
if ($sel <= 0) {
print STDERR "fastrpc: session timed out\n"
# Don't kill the control session while a tcpwrite/tcpread is
# running
my $nk = scalar(keys %xfer_kids);
if ($nk) {
print STDERR "fastrpc[$$]: idle timeout, but $nk ".
"transfer(s) active: ".
join(",", sort keys %xfer_kids)."\n"
if ($gconfig{'rpcdebug'});
next;
}
print STDERR "fastrpc[$$]: session timed out\n"
if ($gconfig{'rpcdebug'});
last;
}
local $line = <SOCK>;
my $line = <SOCK>;
last if (!$line);
local ($len, $auth) = split(/\s+/, $line);
my ($len, $auth) = split(/\s+/, $line);
die "Invalid session ID" if ($auth ne $sid);
local $rawarg;
my $rawarg;
while(length($rawarg) < $len) {
local $got;
local $rv = read(SOCK, $got, $len - length($rawarg));
my $got;
my $rv = read(SOCK, $got, $len - length($rawarg));
exit if ($rv <= 0);
$rawarg .= $got;
}
print STDERR "fastrpc: raw $rawarg\n" if ($gconfig{'rpcdebug'});
local $dumper = substr($rawarg, 0, 5) eq '$VAR1' ? 1 : 0;
local $arg = &unserialise_variable($rawarg);
print STDERR "fastrpc[$$]: raw $rawarg\n" if ($gconfig{'rpcdebug'});
my $dumper = substr($rawarg, 0, 5) eq '$VAR1' ? 1 : 0;
my $arg = &unserialise_variable($rawarg);
# Process it
local $rv;
my $rv;
if ($arg->{'action'} eq 'ping') {
# Just respond with an OK
print STDERR "fastrpc: ping\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: ping\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1 };
}
elsif ($arg->{'action'} eq 'check') {
# Check if some module is supported
print STDERR "fastrpc: check $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: check $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1,
'rv' => &foreign_check($arg->{'module'}, undef, undef,
$arg->{'api'}) };
}
elsif ($arg->{'action'} eq 'config') {
# Get the config for some module
print STDERR "fastrpc: config $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
local %config = &foreign_config($arg->{'module'});
print STDERR "fastrpc[$$]: config $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
my %config = &foreign_config($arg->{'module'});
$rv = { 'status' => 1, 'rv' => \%config };
}
elsif ($arg->{'action'} eq 'write') {
# Transfer data to a local temp file
local $file = $arg->{'file'} ? $arg->{'file'} :
# Transfer data to a my temp file
my $file = $arg->{'file'} ? $arg->{'file'} :
$arg->{'name'} ? &tempname($arg->{'name'}) :
&tempname();
print STDERR "fastrpc: write $file\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: write $file\n" if ($gconfig{'rpcdebug'});
open(FILE, ">$file");
binmode(FILE);
print FILE $arg->{'data'};
@@ -142,24 +176,32 @@ while(1) {
$rv = { 'status' => 1, 'rv' => $file };
}
elsif ($arg->{'action'} eq 'tcpwrite') {
# Transfer data to a local temp file over TCP connection
local $file = $arg->{'file'} ? $arg->{'file'} :
# Transfer data to a my temp file over TCP connection
my $file = $arg->{'file'} ? $arg->{'file'} :
$arg->{'name'} ? &tempname($arg->{'name'}) :
&tempname();
print STDERR "fastrpc: tcpwrite $file\n" if ($gconfig{'rpcdebug'});
local $tsock = time().$$;
local $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
local $tport = $port + 1;
print STDERR "fastrpc[$$]: tcpwrite $file\n" if ($gconfig{'rpcdebug'});
my $tsock = time().$$;
my $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
my $tport = $port + 1;
&allocate_socket($tsock, $tsock6, \$tport);
if (!fork()) {
my $cpid = fork();
if (!defined($cpid)) {
$rv = { 'status' => 0, 'rv' => "fork() failed : $!" };
}
elsif ($cpid == 0) {
close(SOCK);
close(MAIN);
close(MAIN6) if ($use_ipv6);
# Accept connection in separate process
print STDERR "fastrpc: tcpwrite $file port $tport\n" if ($gconfig{'rpcdebug'});
local $rmask;
print STDERR "fastrpc[$$]: tcpwrite $file port $tport\n"
if ($gconfig{'rpcdebug'});
my $rmask;
vec($rmask, fileno($tsock), 1) = 1;
if ($use_ipv6) {
vec($rmask, fileno($tsock6), 1) = 1;
}
local $sel = select($rmask, undef, undef, 30);
my $sel = select($rmask, undef, undef, 30);
exit if ($sel <= 0);
if (vec($rmask, fileno($tsock), 1)) {
accept(TRANS, $tsock) || exit;
@@ -167,40 +209,44 @@ while(1) {
elsif ($use_ipv6 && vec($rmask, fileno($tsock6), 1)) {
accept(TRANS, $tsock6) || exit;
}
print STDERR "fastrpc: tcpwrite $file accepted\n" if ($gconfig{'rpcdebug'});
local $buf;
local $err;
print STDERR "fastrpc[$$]: tcpwrite $file accepted\n" if ($gconfig{'rpcdebug'});
my $buf;
my $err;
if (open(FILE, ">$file")) {
binmode(FILE);
print STDERR "fastrpc: tcpwrite $file writing\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpwrite $file writing\n" if ($gconfig{'rpcdebug'});
my $bs = &get_buffer_size();
while(read(TRANS, $buf, $bs) > 0) {
local $ok = (print FILE $buf);
my $ok = (print FILE $buf);
if (!$ok) {
$err = "Write to $file failed : $!";
last;
}
}
close(FILE);
print STDERR "fastrpc: tcpwrite $file written\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpwrite $file written\n" if ($gconfig{'rpcdebug'});
}
else {
print STDERR "fastrpc: tcpwrite $file open failed $!\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpwrite $file open failed $!\n" if ($gconfig{'rpcdebug'});
$err = "Failed to open $file : $!";
}
print TRANS $err ? "$err\n" : "OK\n";
close(TRANS);
exit;
}
else {
$xfer_kids{$cpid} = 1;
print STDERR "fastrpc[$$]: tcpwrite $file started\n"
if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1, 'rv' => [ $file, $tport ] };
}
close($tsock);
close($tsock6);
print STDERR "fastrpc: tcpwrite $file done\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1, 'rv' => [ $file, $tport ] };
close($tsock6) if ($use_ipv6);
}
elsif ($arg->{'action'} eq 'read') {
# Transfer data from a file
print STDERR "fastrpc: read $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
local ($data, $got);
print STDERR "fastrpc[$$]: read $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
my ($data, $got);
open(FILE, "<$arg->{'file'}");
binmode(FILE);
my $bs = &get_buffer_size();
@@ -212,7 +258,7 @@ while(1) {
}
elsif ($arg->{'action'} eq 'tcpread') {
# Transfer data from a file over TCP connection
print STDERR "fastrpc: tcpread $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpread $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
if (-d $arg->{'file'}) {
$rv = { 'status' => 1, 'rv' => [ undef, "$arg->{'file'} is a directory" ] };
}
@@ -221,18 +267,25 @@ while(1) {
}
else {
binmode(FILE);
local $tsock = time().$$;
local $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
local $tport = $port + 1;
my $tsock = time().$$;
my $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
my $tport = $port + 1;
&allocate_socket($tsock, $tsock6, \$tport);
if (!fork()) {
my $cpid = fork();
if (!defined($cpid)) {
$rv = { 'status' => 0, 'rv' => "fork() failed : $!" };
}
elsif ($cpid == 0) {
close(SOCK);
close(MAIN);
close(MAIN6) if ($use_ipv6);
# Accept connection in separate process
local $rmask;
my $rmask;
vec($rmask, fileno($tsock), 1) = 1;
if ($use_ipv6) {
vec($rmask, fileno($tsock6), 1) = 1;
}
local $sel = select($rmask, undef, undef, 30);
my $sel = select($rmask, undef, undef, 30);
exit if ($sel <= 0);
if (vec($rmask, fileno($tsock), 1)) {
accept(TRANS, $tsock) || exit;
@@ -240,7 +293,7 @@ while(1) {
elsif (vec($rmask, fileno($tsock6), 1)) {
accept(TRANS, $tsock6) || exit;
}
local $buf;
my $buf;
while(read(FILE, $buf, 1024) > 0) {
print TRANS $buf;
}
@@ -248,33 +301,39 @@ while(1) {
close(TRANS);
exit;
}
else {
$xfer_kids{$cpid} = 1;
print STDERR "fastrpc[$$]: tcpread $arg->{'file'} ".
"started\n"
if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1,
'rv' => [ $arg->{'file'}, $tport ] };
}
close(FILE);
close($tsock);
close($tsock6);
print STDERR "fastrpc: tcpread $arg->{'file'} done\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1, 'rv' => [ $arg->{'file'}, $tport ] };
close($tsock6) if ($use_ipv6);
}
}
elsif ($arg->{'action'} eq 'require') {
# require a library
print STDERR "fastrpc: require $arg->{'module'}/$arg->{'file'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: require $arg->{'module'}/$arg->{'file'}\n" if ($gconfig{'rpcdebug'});
eval {
&foreign_require($arg->{'module'},
$arg->{'file'});
};
if ($@) {
print STDERR "fastrpc: require error $@\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: require error $@\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 0, 'rv' => $@ };
}
else {
print STDERR "fastrpc: require done\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: require done\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1 };
}
}
elsif ($arg->{'action'} eq 'call') {
# execute a function
print STDERR "fastrpc: call $arg->{'module'}::$arg->{'func'}(",join(",", @{$arg->{'args'}}),")\n" if ($gconfig{'rpcdebug'});
local @rv;
print STDERR "fastrpc[$$]: call $arg->{'module'}::$arg->{'func'}(",join(",", @{$arg->{'args'}}),")\n" if ($gconfig{'rpcdebug'});
my @rv;
eval {
local $main::error_must_die = 1;
@rv = &foreign_call($arg->{'module'},
@@ -282,7 +341,7 @@ while(1) {
@{$arg->{'args'}});
};
if ($@) {
print STDERR "fastrpc: call error $@\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: call error $@\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 0, 'rv' => $@ };
}
elsif (@rv == 1) {
@@ -291,14 +350,14 @@ while(1) {
else {
$rv = { 'status' => 1, 'arv' => \@rv };
}
print STDERR "fastrpc: call $arg->{'module'}::$arg->{'func'} done = ",join(",", @rv),"\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: call $arg->{'module'}::$arg->{'func'} done = ",join(",", @rv),"\n" if ($gconfig{'rpcdebug'});
}
elsif ($arg->{'action'} eq 'eval') {
# eval some perl code
print STDERR "fastrpc: eval $arg->{'module'} $arg->{'code'}\n" if ($gconfig{'rpcdebug'});
local $erv;
print STDERR "fastrpc[$$]: eval $arg->{'module'} $arg->{'code'}\n" if ($gconfig{'rpcdebug'});
my $erv;
if ($arg->{'module'}) {
local $pkg = $arg->{'module'};
my $pkg = $arg->{'module'};
$pkg =~ s/[^A-Za-z0-9]/_/g;
$erv = eval "package $pkg;\n".
$arg->{'code'}."\n";
@@ -306,7 +365,7 @@ while(1) {
else {
$erv = eval $arg->{'code'};
}
print STDERR "fastrpc: eval $arg->{'module'} $arg->{'code'} done = $rv error = $@\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: eval $arg->{'module'} $arg->{'code'} done = $rv error = $@\n" if ($gconfig{'rpcdebug'});
if ($@) {
$rv = { 'status' => 0, 'rv' => $@ };
}
@@ -315,11 +374,11 @@ while(1) {
}
}
elsif ($arg->{'action'} eq 'quit') {
print STDERR "fastrpc: quit\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: quit\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1 };
}
else {
print STDERR "fastrpc: unknown $arg->{'action'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: unknown $arg->{'action'}\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 0 };
}
$rawrv = &serialise_variable($rv, $dumper);
@@ -334,8 +393,8 @@ while(1) {
# allocate_socket(handle, ipv6-handle, &port)
sub allocate_socket
{
local ($fh, $fh6, $port) = @_;
local $proto = getprotobyname('tcp');
my ($fh, $fh6, $port) = @_;
my $proto = getprotobyname('tcp');
if (!socket($fh, PF_INET, SOCK_STREAM, $proto)) {
return "socket failed : $!";
}

View File

@@ -14,7 +14,7 @@ if( $in{ 'action' } eq $text{ 'hdparm_apply' } )
{
$command .= "-".$key." ".quotemeta($in{$key})." " if ($in{$key} ne "");
}
$command .= $in{ 'drive' }."\n";
$command .= quotemeta($in{ 'drive' })."\n";
local $out = "<p>". $text{ 'hdparm_performing' }. " : <b>". $command. "</b><i>". &backquote_logged($command). "</i><p>";
$out =~ s/\n/<br>/g;
@@ -24,7 +24,8 @@ if( $in{ 'action' } eq $text{ 'hdparm_apply' } )
} else {
&ui_print_header(undef, $text{'hdparm_speed'}, "");
local ( $_, $_, $buffered, $buffer_cache ) = split( /\n/, `hdparm -t -T $in{ 'drive' }` );
local $qdrive = quotemeta($in{ 'drive' });
local ( $_, $_, $buffered, $buffer_cache ) = split( /\n/, `hdparm -t -T $qdrive` );
( $_, $buffered ) = split( /=/, $buffered );
( $_, $buffer_cache ) = split( /=/, $buffer_cache );

View File

@@ -18,10 +18,12 @@ if ( ! &has_command( "hdparm" ) ) {
%hdparm = ( 'A', "1", 'K', "0", 'P', "0", 'X', "0", 'W', "0", 'S', "0" );
@yesno = ( "1", $text{ 'hdparm_on' }, "0", $text{ 'hdparm_off' } );
local $qdevice = quotemeta($d->{'device'});
foreach $argument ( 'a', 'd', 'r', 'k', 'u', 'm', 'c' )
{
$out = `hdparm -$argument $d->{'device'}`;
local $qargument = quotemeta($argument);
$out = `hdparm -$qargument $qdevice`;
if ($out =~ /\s+=\s+(\S+)/) {
$hdparm{ $argument } = $1;
}

View File

@@ -241,14 +241,15 @@ foreach my $id (readdir(IDS)) {
closedir(IDS);
# Call fdisk to get partition and geometry information
local $devs = join(" ", @devs);
local $qdevs = join(" ", map { quotemeta($_) } @devs);
local ($disk, $m2);
if ($has_parted) {
open(FDISK, join(" ; ",
map { "parted $_ unit cyl print 2>/dev/null" } @devs)." |");
map { "parted ".quotemeta($_)." unit cyl print 2>/dev/null" }
@devs)." |");
}
else {
open(FDISK, "fdisk -l -u=cylinders $devs 2>/dev/null || fdisk -l $devs 2>/dev/null |");
open(FDISK, "fdisk -l -u=cylinders $qdevs 2>/dev/null || fdisk -l $qdevs 2>/dev/null |");
}
while(<FDISK>) {
if (($m4 = ($_ =~ /Disk\s+([^ :]+):\s+([\d\.]+)\s+(\S+),\s+(\d+)\s+bytes,\s+(\d+)\s+sectors/)) ||
@@ -736,7 +737,7 @@ sub delete_partition
my ($disk, $part) = @_;
if ($has_parted) {
# Using parted
my $cmd = "parted -s ".$disk." rm ".$part;
my $cmd = "parted -s ".quotemeta($disk)." rm ".quotemeta($part);
my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) {
&error("$cmd failed : $out");
@@ -765,19 +766,23 @@ my ($disk, $part, $start, $end, $type) = @_;
if ($has_parted) {
# Using parted
my $pe = $part > 4 ? "logical" : "primary";
my $qdisk = quotemeta($disk);
my $qpart = quotemeta($part);
my $qstart = quotemeta($start-1);
my $qend = quotemeta($end);
my $cmd;
if ($type eq "raid") {
$cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ".
"ext2 ".($start-1)." ".$end;
$cmd .= " ; parted -s ".$disk." set $part raid on";
$cmd = "parted -s ".$qdisk." unit cyl mkpart ".$pe." ".
"ext2 ".$qstart." ".$qend;
$cmd .= " ; parted -s ".$qdisk." set $qpart raid on";
}
elsif ($type && $type ne 'ext2') {
$cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ".
$type." ".($start-1)." ".$end;
$cmd = "parted -s ".$qdisk." unit cyl mkpart ".$pe." ".
quotemeta($type)." ".$qstart." ".$qend;
}
else {
$cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ".
($start-1)." ".$end;
$cmd = "parted -s ".$qdisk." unit cyl mkpart ".$pe." ".
$qstart." ".$qend;
}
my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) {
@@ -830,8 +835,8 @@ sub create_extended
my ($disk, $part, $start, $end) = @_;
if ($has_parted) {
# Create using parted
my $cmd = "parted -s ".$disk." unit cyl mkpart extended ".
($start-1)." ".$end;
my $cmd = "parted -s ".quotemeta($disk)." unit cyl mkpart ".
"extended ".quotemeta($start-1)." ".quotemeta($end);
my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) {
&error("$cmd failed : $out");
@@ -1027,7 +1032,8 @@ elsif ($_[0] eq "btrfs") {
# given device. Options are taken from %in.
sub mkfs_parse
{
local($cmd);
local($cmd, $qdev);
$qdev = quotemeta($_[1]);
if ($_[0] eq "msdos" || $_[0] eq "vfat") {
$cmd = "mkfs -t $_[0]";
$cmd .= &opt_check("msdos_ff", '[1-2]', "-f");
@@ -1035,17 +1041,17 @@ if ($_[0] eq "msdos" || $_[0] eq "vfat") {
$in{'msdos_F_other'} =~ /^\d+$/ ||
&error(&text('opt_error', $in{'msdos_F_other'},
$text{'msdos_F'}));
$cmd .= " -F ".$in{'msdos_F_other'};
$cmd .= " -F ".quotemeta($in{'msdos_F_other'});
}
elsif ($in{'msdos_F'}) {
$cmd .= " -F ".$in{'msdos_F'};
$cmd .= " -F ".quotemeta($in{'msdos_F'});
}
$cmd .= &opt_check("msdos_i", '[0-9a-f]{8}', "-i");
$cmd .= &opt_check("msdos_n", '\S{1,11}', "-n");
$cmd .= &opt_check("msdos_r", '\d+', "-r");
$cmd .= &opt_check("msdos_s", '\d+', "-s");
$cmd .= $in{'msdos_c'} ? " -c" : "";
$cmd .= " $_[1]";
$cmd .= " $qdev";
}
elsif ($_[0] eq "minix") {
local(@plist, $disk, $part, $i, @pinfo);
@@ -1054,13 +1060,13 @@ elsif ($_[0] eq "minix") {
$cmd .= &opt_check("minix_i", '\d+', "-i ");
$cmd .= $in{'minix_c'} ? " -c" : "";
$cmd .= &opt_check("minix_b", '\d+', " ");
$cmd .= " $_[1]";
$cmd .= " $qdev";
}
elsif ($_[0] eq "reiserfs") {
$cmd = "yes | mkreiserfs";
$cmd .= " -f" if ($in{'reiserfs_f'});
$cmd .= " -h $in{'reiserfs_h'}" if ($in{'reiserfs_h'});
$cmd .= " $_[1]";
$cmd .= " -h ".quotemeta($in{'reiserfs_h'}) if ($in{'reiserfs_h'});
$cmd .= " $qdev";
}
elsif ($_[0] =~ /^ext\d+$/) {
if (&has_command("mkfs.$_[0]")) {
@@ -1081,7 +1087,7 @@ elsif ($_[0] =~ /^ext\d+$/) {
$in{'ext3_j'} =~ /^\d+$/ ||
&error(&text('opt_error', $in{'ext3_j'},
$text{'ext3_j'}));
$cmd .= " -J size=$in{'ext3_j'}";
$cmd .= " -J size=".quotemeta($in{'ext3_j'});
}
}
$cmd .= &opt_check("ext2_b", '\d+', "-b");
@@ -1090,29 +1096,29 @@ elsif ($_[0] =~ /^ext\d+$/) {
$cmd .= &opt_check("ext2_m", '\d+', "-m");
$cmd .= $in{'ext2_c'} ? " -c" : "";
$cmd .= " -q";
$cmd .= " $_[1]";
$cmd .= " $qdev";
}
elsif ($_[0] eq "xfs") {
$cmd = "mkfs -t $_[0]";
$cmd .= " -f" if ($in{'xfs_f'});
$cmd .= " -b size=$in{'xfs_b'}" if (!$in{'xfs_b_def'});
$cmd .= " $_[1]";
$cmd .= " -b size=".quotemeta($in{'xfs_b'}) if (!$in{'xfs_b_def'});
$cmd .= " $qdev";
}
elsif ($_[0] eq "jfs") {
$cmd = "mkfs -t $_[0] -q";
$cmd .= &opt_check("jfs_s", '\d+', "-s");
$cmd .= " -c" if ($in{'jfs_c'});
$cmd .= " $_[1]";
$cmd .= " $qdev";
}
elsif ($_[0] eq "fatx") {
$cmd = "mkfs -t $_[0] $_[1]";
$cmd = "mkfs -t $_[0] $qdev";
}
elsif ($_[0] eq "btrfs") {
$cmd = "mkfs -t $_[0]";
$cmd .= " -l $in{'btrfs_l'}" if (!$in{'btrfs_l_def'});
$cmd .= " -n $in{'btrfs_n'}" if (!$in{'btrfs_n_def'});
$cmd .= " -s $in{'btrfs_s'}" if (!$in{'btrfs_s_def'});
$cmd .= " $_[1]";
$cmd .= " -l ".quotemeta($in{'btrfs_l'}) if (!$in{'btrfs_l_def'});
$cmd .= " -n ".quotemeta($in{'btrfs_n'}) if (!$in{'btrfs_n_def'});
$cmd .= " -s ".quotemeta($in{'btrfs_s'}) if (!$in{'btrfs_s_def'});
$cmd .= " $qdev";
}
if (&has_command("partprobe")) {
$cmd = "partprobe ; $cmd";
@@ -1170,16 +1176,17 @@ if ($_[0] =~ /^ext\d+$/) {
# Returns the tuning command based on user inputs
sub tunefs_parse
{
local $qdev = quotemeta($_[1]);
if ($_[0] =~ /^ext\d+$/) {
$cmd = "tune2fs";
$cmd .= &opt_check("tunefs_c", '\d+', "-c");
$cmd .= $in{'tunefs_e_def'} ? "" : " -e$in{'tunefs_e'}";
$cmd .= $in{'tunefs_u_def'} ? "" : " -u".getpwnam($in{'tunefs_u'});
$cmd .= $in{'tunefs_g_def'} ? "" : " -g".getgrnam($in{'tunefs_g'});
$cmd .= $in{'tunefs_e_def'} ? "" : " -e".quotemeta($in{'tunefs_e'});
$cmd .= $in{'tunefs_u_def'} ? "" : " -u".quotemeta(getpwnam($in{'tunefs_u'}));
$cmd .= $in{'tunefs_g_def'} ? "" : " -g".quotemeta(getgrnam($in{'tunefs_g'}));
$cmd .= &opt_check("tunefs_m",'\d+',"-m");
$cmd .= &opt_check("tunefs_i", '\d+', "-i").
($in{'tunefs_i_def'} ? "" : $in{'tunefs_i_unit'});
$cmd .= " $_[1]";
($in{'tunefs_i_def'} ? "" : quotemeta($in{'tunefs_i_unit'}));
$cmd .= " $qdev";
}
return $cmd;
}
@@ -1316,11 +1323,12 @@ return ($_[0] =~ /^ext\d+$/ && &has_command("fsck.$_[0]") ||
# Returns the fsck command to unconditionally check a filesystem
sub fsck_command
{
local $qdev = quotemeta($_[1]);
if ($_[0] =~ /^ext\d+$/) {
return "fsck -t $_[0] -p $_[1]";
return "fsck -t $_[0] -p $qdev";
}
elsif ($_[0] eq "minix") {
return "fsck -t minix -a $_[1]";
return "fsck -t minix -a $qdev";
}
}
@@ -1436,21 +1444,27 @@ else {
sub open_fdisk
{
local $fpath = &check_fdisk();
local $qfpath = quotemeta($fpath);
my $cylarg;
if ($fpath =~ /\/fdisk/) {
my $out = &backquote_command("$fpath -h 2>&1 </dev/null");
my $out = &backquote_command("$qfpath -h 2>&1 </dev/null");
if ($out =~ /-u\s+<size>/) {
$cylarg = "-u=cylinders";
}
}
local @qargs = map { quotemeta($_) } grep { defined($_) && $_ ne "" }
($fpath, $cylarg, @_);
($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
join(" ", $fpath, $cylarg, @_));
join(" ", @qargs));
}
sub open_sfdisk
{
local $sfpath = &has_command("sfdisk");
($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$sfpath, @_));
local @qargs = map { quotemeta($_) } grep { defined($_) && $_ ne "" }
($sfpath, @_);
($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
join(" ", @qargs));
}
sub check_fdisk
@@ -1482,7 +1496,7 @@ if ($in{"$_[0]_def"}) { return ""; }
elsif ($in{$_[0]} !~ /^$_[1]$/) {
&error(&text('opt_error', $in{$_[0]}, $text{$_[0]}));
}
else { return " $_[2] $in{$_[0]}"; }
else { return " $_[2] ".quotemeta($in{$_[0]}); }
}
%tags = ('0', 'Empty',
@@ -1611,7 +1625,8 @@ return 0;
sub disk_space
{
local $w = $_[1] || $_[0];
local $out = `df -k '$w'`;
local $qw = quotemeta($w);
local $out = `df -k $qw`;
if ($out =~ /Mounted on\s*\n\s*\S+\s+(\S+)\s+\S+\s+(\S+)/i) {
return ($1, $2);
}
@@ -1649,19 +1664,21 @@ return @fstypes;
sub get_label
{
local $label;
local $qdev = quotemeta($_[0]);
if ($has_e2label) {
$label = `e2label $_[0] 2>&1`;
$label = `e2label $qdev 2>&1`;
chop($label);
}
if (($? || $label !~ /\S/) && $has_xfs_db) {
$label = undef;
local $out = &backquote_with_timeout("xfs_db -x -p xfs_admin -c label -r $_[0] 2>&1", 5);
local $out = &backquote_with_timeout(
"xfs_db -x -p xfs_admin -c label -r $qdev 2>&1", 5);
$label = $1 if ($out =~ /label\s*=\s*"(.*)"/ &&
$1 ne '(null)');
}
if (($? || $label !~ /\S/) && $has_reiserfstune) {
$label = undef;
local $out = &backquote_command("reiserfstune $_[0]");
local $out = &backquote_command("reiserfstune $qdev");
if ($out =~ /LABEL:\s*(\S+)/) {
$label = $1;
}
@@ -1703,12 +1720,15 @@ return $uuid;
# Tries to set the label for some device's filesystem
sub set_label
{
local $qdev = quotemeta($_[0]);
local $qlabel = quotemeta($_[1]);
if ($has_e2label && ($_[2] =~ /^ext[23]$/ || !$_[2])) {
&system_logged("e2label '$_[0]' '$_[1]' >/dev/null 2>&1");
&system_logged("e2label $qdev $qlabel >/dev/null 2>&1");
return 1 if (!$?);
}
if ($has_xfs_db && ($_[2] eq "xfs" || !$_[2])) {
&system_logged("xfs_db -x -p xfs_admin -c \"label $_[1]\" $_[0] >/dev/null 2>&1");
&system_logged(
"xfs_db -x -p xfs_admin -c \"label $qlabel\" $qdev >/dev/null 2>&1");
return 1 if (!$?);
}
return 0;
@@ -1719,7 +1739,8 @@ return 0;
sub set_name
{
my ($dinfo, $pinfo, $name) = @_;
my $cmd = "parted -s ".$dinfo->{'device'}." name ".$pinfo->{'number'}." ";
my $cmd = "parted -s ".quotemeta($dinfo->{'device'})." name ".
quotemeta($pinfo->{'number'})." ";
if ($name) {
$cmd .= quotemeta($name);
}
@@ -1737,7 +1758,7 @@ if ($?) {
sub set_partition_table
{
my ($disk, $table) = @_;
my $cmd = "parted -s ".$disk." mktable ".$table;
my $cmd = "parted -s ".quotemeta($disk)." mktable ".quotemeta($table);
my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) {
&error("$cmd failed : $out");

View File

@@ -17,14 +17,14 @@ else {
&ui_print_unbuffered_header($uheader, $text{'check_title'}, "");
$cmd = "$config{'fetchmail_path'} -v -f '$file'";
$cmd = "$config{'fetchmail_path'} -v -f ".quotemeta($file);
if ($config{'mda_command'}) {
$cmd .= " -m '$config{'mda_command'}'";
$cmd .= " -m ".quotemeta($config{'mda_command'});
}
if (defined($in{'idx'})) {
@conf = &parse_config_file($file);
$poll = $conf[$in{'idx'}];
$cmd .= " $poll->{'poll'}";
$cmd .= " ".quotemeta($poll->{'poll'});
}
print &text('check_exec', "<tt>$cmd</tt>"),"<p>\n";

View File

@@ -19,10 +19,10 @@ print &ui_table_start($text{'cron_header'}, "width=100%", 2);
if ($job) {
if ($job->{'command'} =~ /--mail\s+(\S+)/) {
$mail = `echo $1`;
($mail = $1) =~ s/\\(.)/$1/g;
}
elsif ($job->{'command'} =~ /--file\s+(\S+)/) {
$file = `echo $1`;
($file = $1) =~ s/\\(.)/$1/g;
}
elsif ($job->{'command'} =~ /--output/) {
$output = 1;
@@ -31,7 +31,7 @@ if ($job) {
$owner = 1;
}
if ($job->{'command'} =~ /--user\s+(\S+)/) {
$user = $1;
($user = $1) =~ s/\\(.)/$1/g;
}
if ($job->{'command'} =~ /--errors/) {
$errors = 1;

View File

@@ -36,7 +36,7 @@ if ($in{'errors'}) {
}
if ($cron_user eq "root" && $fetchmail_config) {
defined(getpwnam($in{'user'})) || &error($text{'cron_euser'});
$cmd .= " --user $in{'user'}";
$cmd .= " --user ".quotemeta($in{'user'});
}
if ($job && $in{'enabled'}) {

View File

@@ -51,9 +51,9 @@ if ($found) {
else {
&create_poll($poll, $file);
if ($in{'user'} && $< == 0) {
&system_logged("chown $in{'user'} $file");
&system_logged("chown ".quotemeta($in{'user'})." ".quotemeta($file));
}
&system_logged("chmod 700 $file");
&system_logged("chmod 700 ".quotemeta($file));
}
&unlock_file($file);
&webmin_log("global", undef, $config{'config_file'} ? $file : $in{'user'},

View File

@@ -101,9 +101,9 @@ else {
&create_poll($poll, $file);
if ($in{'user'} && $< == 0) {
local @uinfo = getpwnam($in{'user'});
&system_logged("chown $uinfo[2]:$uinfo[3] $file");
&system_logged("chown $uinfo[2]:$uinfo[3] ".quotemeta($file));
}
&system_logged("chmod 700 $file");
&system_logged("chmod 700 ".quotemeta($file));
}
else {
&modify_poll($poll, $file);

View File

@@ -13,17 +13,21 @@ if ($config{'start_cmd'}) {
}
else {
$in{'interval'} =~ /^\d+$/ || &error($text{'start_einterval'});
$mda = " -m '$config{'mda_command'}'" if ($config{'mda_command'});
$mda = " -m ".quotemeta($config{'mda_command'}) if ($config{'mda_command'});
my $qinterval = quotemeta($in{'interval'});
my $qconfig_file = quotemeta($config{'config_file'});
if ($< == 0) {
if ($config{'daemon_user'} eq 'root') {
$out = &backquote_logged("$config{'fetchmail_path'} -d $in{'interval'} -f $config{'config_file'} $mda 2>&1");
$out = &backquote_logged("$config{'fetchmail_path'} -d $qinterval -f $qconfig_file $mda 2>&1");
}
else {
$out = &backquote_logged("su - '$config{'daemon_user'}' -c '$config{'fetchmail_path'} -d $in{'interval'} -f $config{'config_file'} $mda' 2>&1");
my $qdaemon_user = quotemeta($config{'daemon_user'});
my $daemon_cmd = "$config{'fetchmail_path'} -d $qinterval -f $qconfig_file $mda";
$out = &backquote_logged("su - $qdaemon_user -c ".quotemeta($daemon_cmd)." 2>&1");
}
}
else {
$out = &backquote_logged("$config{'fetchmail_path'} -d $in{'interval'} $mda 2>&1");
$out = &backquote_logged("$config{'fetchmail_path'} -d $qinterval $mda 2>&1");
}
}
if ($?) {

View File

@@ -16,7 +16,8 @@ elsif ($< == 0) {
$out = &backquote_logged("$config{'fetchmail_path'} -q 2>&1");
}
else {
$out = &backquote_logged("su - '$config{'daemon_user'}' -c '$config{'fetchmail_path'} -q' 2>&1");
my $qdaemon_user = quotemeta($config{'daemon_user'});
$out = &backquote_logged("su - $qdaemon_user -c ".quotemeta("$config{'fetchmail_path'} -q")." 2>&1");
}
}
else {

View File

@@ -7,8 +7,9 @@ $access{'view'} && &error($text{'ecannot'});
&ReadParse();
&can_edit_disk($in{'dev'}) || &error($text{'fsck_ecannot'});
&ui_print_header(undef, $text{'fsck_title'}, "");
$in{'mode'} =~ /^-(m|n|y)$/ || &error($text{'fsck_ecannot'});
$in{dev} =~ s/dsk/rdsk/g;
$cmd = "fsck -F ufs $in{mode} $in{dev}";
$cmd = "fsck -F ufs $in{mode} ".quotemeta($in{dev});
print &text('fsck_exec', "<tt>$cmd</tt>"),"<p>\n";
print "<pre>\n";

View File

@@ -16,13 +16,15 @@ $cmd .= &opt_check("ufs_f", '\d+', "-f");
$cmd .= &opt_check("ufs_i", '\d+', "-i");
$cmd .= &opt_check("ufs_m", '\d+', "-m");
$cmd .= &opt_check("ufs_n", '\d+', "-n");
$in{ufs_o} =~ /^(space|time)$/ || !$in{ufs_o} ||
&error($text{'newfs_ecannot'});
$cmd .= $in{ufs_o} ? " -o $in{ufs_o}" : "";
$cmd .= &opt_check("ufs_r", '\d+', "-r");
$cmd .= &opt_check("ufs_s", '\d+', "-s");
$cmd .= &opt_check("ufs_t", '\d+', "-t");
$cmd .= &opt_check("ufs_cb", '\d+', "-C");
$in{dev} =~ s/dsk/rdsk/g;
$cmd .= " $in{dev}";
$cmd .= " ".quotemeta($in{dev});
&ui_print_header(undef, $text{'newfs_title'}, "");

View File

@@ -13,9 +13,11 @@ $cmd .= &opt_check("tunefs_a", '\d+', "-a");
$cmd .= &opt_check("tunefs_d", '\d+', "-d");
$cmd .= &opt_check("tunefs_e", '\d+', "-e");
$cmd .= &opt_check("tunefs_m", '\d+', "-m");
$in{tunefs_o} =~ /^(space|time)$/ || !$in{tunefs_o} ||
&error($text{'tunefs_ecannot'});
$cmd .= $in{tunefs_o} ? " -o $in{tunefs_o}" : "";
$in{dev} =~ s/dsk/rdsk/g;
$cmd .= " $in{dev}";
$cmd .= " ".quotemeta($in{dev});
&ui_print_header(undef, $text{'tunefs_title'}, "");

View File

@@ -132,7 +132,6 @@ if ($out && $dump->{'email'} && &foreign_check("mailboxes")) {
# Check for any dumps scheduled to run after this one
foreach $follow (&list_dumps()) {
if ($follow->{'follow'} eq $dump->{'id'} && $follow->{'enabled'} == 2) {
system("$cron_cmd $follow->{'id'}");
system("$cron_cmd ".quotemeta($follow->{'id'}));
}
}

View File

@@ -177,14 +177,15 @@ local $fh = $_[1];
local ($cmd, $flags);
if ($_[0]->{'huser'}) {
$flags = "-f '$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4])."'";
$flags = "-f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
}
elsif ($_[0]->{'host'}) {
$flags = "-f '$_[0]->{'host'}:".&date_subs($_[0]->{'hfile'}, $_[4])."'";
$flags = "-f ".quotemeta("$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
}
else {
$flags = "-f '".&date_subs($_[0]->{'file'}, $_[4])."'";
$flags = "-f ".quotemeta(&date_subs($_[0]->{'file'}, $_[4]));
}
local $tapecmd = $_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar' ? $multi_cmd :
$_[0]->{'multi'} ? undef :
@@ -192,16 +193,16 @@ local $tapecmd = $_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar' ? $multi_cmd :
if ($_[0]->{'fs'} eq 'tar') {
# Construct tar command
$cmd = "$tar_command -c $flags";
$cmd .= " -V '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -V ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -L $_[0]->{'blocks'}" if ($_[0]->{'blocks'});
$cmd .= " -z" if ($_[0]->{'gzip'});
$cmd .= " -M" if ($_[0]->{'multi'});
$cmd .= " -h" if ($_[0]->{'links'});
$cmd .= " -l" if ($_[0]->{'xdev'});
$cmd .= " -F \"$tapecmd $_[0]->{'id'}\""
$cmd .= " -F ".quotemeta("$tapecmd $_[0]->{'id'}")
if (!$_[0]->{'gzip'} && ($_[0]->{'file'} =~ /^\/dev/ ||
$_[0]->{'hfile'} =~ /^\/dev/));
$cmd .= " --rsh-command=$_[0]->{'rsh'}"
$cmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'})
if ($_[0]->{'rsh'} && $_[0]->{'host'});
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " ".quotemeta(&date_subs($_[0]->{'dir'}));
@@ -227,7 +228,8 @@ $ENV{'DUMP_PASSWORD'} = $_[0]->{'pass'};
local $got = &run_ssh_command($cmd, $fh, $_[2], $_[0]->{'pass'});
if ($_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar') {
# Run multi-file switch command one last time
&execute_command("$multi_cmd $_[0]->{'id'} >/dev/null 2>&1");
&execute_command("$multi_cmd ".quotemeta($_[0]->{'id'}).
" >/dev/null 2>&1");
}
return $got ? 0 : 1;
}
@@ -347,7 +349,7 @@ else {
}
if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'});
$cmd .= " -f '$in{'file'}'";
$cmd .= " -f ".quotemeta($in{'file'});
}
else {
&to_ipaddress($in{'host'}) ||
@@ -356,10 +358,10 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) {
$cmd .= " -f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'";
$cmd .= " -f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
}
else {
$cmd .= " -f '$in{'host'}:$in{'hfile'}'";
$cmd .= " -f ".quotemeta("$in{'host'}:$in{'hfile'}");
}
}
@@ -372,7 +374,7 @@ if ($_[0] eq 'tar') {
!-c $in{'file'} && !-b $in{'file'} ||
&error($text{'restore_emulti'});
$in{'mode'} == 0 || &error($text{'restore_emulti2'});
$cmd .= " -M -F \"$rmulti_cmd $in{'file'}\"";
$cmd .= " -M -F ".quotemeta("$rmulti_cmd $in{'file'}");
}
local $rsh = &rsh_command_parse("rsh_def", "rsh");
if ($rsh) {
@@ -384,9 +386,9 @@ if ($_[0] eq 'tar') {
$cmd .= " $in{'files'}";
}
-d $in{'dir'} || &error($text{'restore_edir'});
$cmd = "cd '$in{'dir'}' && $cmd";
$cmd = "cd ".quotemeta($in{'dir'})." && $cmd";
if ($in{'multi'}) {
$cmd = "$rmulti_cmd $in{'file'} 1 && $cmd";
$cmd = "$rmulti_cmd ".quotemeta($in{'file'})." 1 && $cmd";
}
}
else {
@@ -412,7 +414,8 @@ $ENV{'DUMP_PASSWORD'} = $in{'pass'};
# Need to supply prompts
&foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume;
while(1) {
local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "password:", "yes\\/no", "(.*\\n)");

View File

@@ -121,28 +121,29 @@ sub execute_dump
local $fh = $_[1];
local ($cmd, $flag);
if ($_[0]->{'huser'}) {
$flag = " -f '$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4])."'";
$flag = " -f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
}
elsif ($_[0]->{'host'}) {
$flag = " -f '$_[0]->{'host'}:".&date_subs($_[0]->{'hfile'}, $_[4])."'";
$flag = " -f ".quotemeta("$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
}
else {
$flag = " -f '".&date_subs($_[0]->{'file'}, $_[4])."'";
$flag = " -f ".quotemeta(&date_subs($_[0]->{'file'}, $_[4]));
}
$cmd = "xfsdump -l $_[0]->{'level'}";
$cmd .= $flag;
$cmd .= " -L '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -M '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -z '$_[0]->{'max'}'" if ($_[0]->{'max'});
$cmd .= " -L ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -M ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -z ".quotemeta($_[0]->{'max'}) if ($_[0]->{'max'});
$cmd .= " -A" if ($_[0]->{'noattribs'});
$cmd .= " -F" if ($_[0]->{'over'});
$cmd .= " -J" if ($_[0]->{'noinvent'});
$cmd .= " -o" if ($_[0]->{'overwrite'});
$cmd .= " -c \"$_[3] $_[0]->{'id'}\"" if ($_[3]);
$cmd .= " -c ".quotemeta("$_[3] $_[0]->{'id'}") if ($_[3]);
$cmd .= " -E -F" if ($_[0]->{'erase'});
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " '".&date_subs($_[0]->{'dir'})."'";
$cmd .= " ".quotemeta(&date_subs($_[0]->{'dir'}));
&system_logged("sync");
sleep(1);
@@ -229,7 +230,7 @@ local $cmd = "xfsrestore";
$cmd .= " -t" if ($in{'test'});
if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'});
$cmd .= " -f '$in{'file'}'";
$cmd .= " -f ".quotemeta($in{'file'});
}
else {
&to_ipaddress($in{'host'}) ||
@@ -238,21 +239,21 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) {
$cmd .= " -f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'";
$cmd .= " -f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
}
else {
$cmd .= " -f '$in{'host'}:$in{'hfile'}'";
$cmd .= " -f ".quotemeta("$in{'host'}:$in{'hfile'}");
}
}
$cmd .= " -E" if ($in{'over'} == 1);
$cmd .= " -e" if ($in{'over'} == 2);
$cmd .= " -A" if ($in{'noattribs'});
$cmd .= " -L '$in{'label'}'" if ($in{'label'});
$cmd .= " -L ".quotemeta($in{'label'}) if ($in{'label'});
$cmd .= " -F";
$cmd .= " $in{'extra'}" if ($in{'extra'});
if (!$in{'test'}) {
-d $in{'dir'} || &error($text{'restore_edir'});
$cmd .= " '$in{'dir'}'";
$cmd .= " ".quotemeta($in{'dir'});
}
return $cmd;
}
@@ -274,7 +275,8 @@ if ($_[0] eq 'xfs') {
else {
# Need to supply prompts
&foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume;
while(1) {
local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "(.*\\n)");

View File

@@ -343,6 +343,19 @@ else {
$_[0]->{'remount'} = $in{'remount'};
}
# split_shell_words(string)
# Splits command-line text to shell words while rejecting line breaks
sub split_shell_words
{
my ($str) = @_;
return () if (!defined($str) || $str !~ /\S/);
my @words = &split_quoted_string($str);
foreach my $w (@words) {
$w =~ /[\r\n\0]/ && &error("Invalid command-line parameter");
}
return @words;
}
# execute_dump(&dump, filehandle, escape, background-mode, [time])
# Executes a dump and displays the output
sub execute_dump
@@ -360,7 +373,7 @@ local @dirs = $_[0]->{'tabs'} ? split(/\t+/, $_[0]->{'dir'})
if ($_[0]->{'fs'} eq 'tar') {
# tar format backup
$cmd = "tar ".($_[0]->{'update'} ? "-u" : "-c")." ".$flag;
$cmd .= " -V '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -V ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -L $_[0]->{'blocks'}" if ($_[0]->{'blocks'});
$cmd .= " -z" if ($_[0]->{'gzip'} == 1);
$cmd .= " --bzip" if ($_[0]->{'gzip'} == 2);
@@ -368,7 +381,7 @@ if ($_[0]->{'fs'} eq 'tar') {
$cmd .= " -M" if ($_[0]->{'multi'});
$cmd .= " -h" if ($_[0]->{'links'});
$cmd .= " --one-file-system" if ($_[0]->{'xdev'});
$cmd .= " -F \"$tapecmd $_[0]->{'id'}\""
$cmd .= " -F ".quotemeta("$tapecmd $_[0]->{'id'}")
if (!$_[0]->{'gzip'} && $tapecmd);
$cmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'})
if ($_[0]->{'rsh'} && $_[0]->{'host'});
@@ -381,24 +394,26 @@ if ($_[0]->{'fs'} eq 'tar') {
$cmd .= " --exclude ".quotemeta($e);
}
}
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " ".join(" ", map { "'$_'" } @dirs);
my @extra = &split_shell_words($_[0]->{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
$cmd .= " ".join(" ", map { quotemeta($_) } @dirs);
}
elsif ($_[0]->{'fs'} eq 'xfs') {
# xfs backup
$cmd = "xfsdump -l $_[0]->{'level'}";
$cmd .= $flag;
$cmd .= " -L '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -M '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -z '$_[0]->{'max'}'" if ($_[0]->{'max'});
$cmd .= " -L ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -M ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -z ".quotemeta($_[0]->{'max'}) if ($_[0]->{'max'});
$cmd .= " -A" if ($_[0]->{'noattribs'});
$cmd .= " -F" if ($_[0]->{'over'});
$cmd .= " -J" if ($_[0]->{'noinvent'});
$cmd .= " -o" if ($_[0]->{'overwrite'});
$cmd .= " -E -F" if ($_[0]->{'erase'});
$cmd .= " -b $_[0]->{'bsize'}" if ($_[0]->{'bsize'});
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " ".join(" ", map { "'$_'" } @dirs);
my @extra = &split_shell_words($_[0]->{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
$cmd .= " ".join(" ", map { quotemeta($_) } @dirs);
}
else {
# ext2/3 backup
@@ -406,19 +421,21 @@ else {
$cmd .= $flag;
$cmd .= " -u" if ($_[0]->{'update'});
$cmd .= " -M" if ($_[0]->{'multi'});
$cmd .= " -L '$_[0]->{'label'}'" if ($_[0]->{'label'});
$cmd .= " -L ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -B $_[0]->{'blocks'}" if ($_[0]->{'blocks'});
$cmd .= " -b $_[0]->{'bsize'}" if ($_[0]->{'bsize'});
$cmd .= " -h0" if ($_[0]->{'honour'});
$cmd .= " -j$_[0]->{'comp'}" if ($_[0]->{'comp'});
$cmd .= " -F \"$tapecmd $_[0]->{'id'}\"" if ($tapecmd);
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " '$_[0]->{'dir'}'";
$cmd .= " -F ".quotemeta("$tapecmd $_[0]->{'id'}") if ($tapecmd);
my @extra = &split_shell_words($_[0]->{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
$cmd .= " ".quotemeta($_[0]->{'dir'});
if ($_[0]->{'rsh'}) {
$cmd = "RSH=\"$_[0]->{'rsh'}\" RMT=\"touch '$hfile'; /etc/rmt\" $cmd";
$cmd = "RSH=".quotemeta($_[0]->{'rsh'})." ".
"RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $cmd";
}
else {
$cmd = "RMT=\"touch '$hfile'; /etc/rmt\" $cmd";
$cmd = "RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $cmd";
}
}
@@ -444,7 +461,8 @@ $ENV{'DUMP_PASSWORD'} = $_[0]->{'pass'};
local $got = &run_ssh_command($cmd, $fh, $_[2], $_[0]->{'pass'});
if ($_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar') {
# Run multi-file switch command one last time
&execute_command("$multi_cmd $_[0]->{'id'} >/dev/null 2>&1");
&execute_command("$multi_cmd ".quotemeta($_[0]->{'id'}).
" >/dev/null 2>&1");
}
# Remount with atime option
@@ -467,14 +485,14 @@ sub dump_flag
local ($flag, $hfile);
if ($_[0]->{'huser'}) {
$hfile = &date_subs($_[0]->{'hfile'}, $_[1]);
$flag = " -f '$_[0]->{'huser'}\@$_[0]->{'host'}:$hfile'";
$flag = " -f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:$hfile");
}
elsif ($_[0]->{'host'}) {
$hfile = &date_subs($_[0]->{'hfile'}, $_[1]);
$flag = " -f '$_[0]->{'host'}:$hfile'";
$flag = " -f ".quotemeta("$_[0]->{'host'}:$hfile");
}
else {
$flag = " -f '".&date_subs($_[0]->{'file'}, $_[1])."'";
$flag = " -f ".quotemeta(&date_subs($_[0]->{'file'}, $_[1]));
}
return ($flag, $hfile);
}
@@ -503,15 +521,16 @@ else {
}
$vcmd .= $flag;
if ($_[0]->{'fs'} eq "tar") {
$vcmd .= " --rsh-command=$_[0]->{'rsh'}"
$vcmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'})
if ($_[0]->{'rsh'} && $_[0]->{'host'});
}
elsif ($_[0]->{'fs'} ne "xfs") {
if ($_[0]->{'rsh'}) {
$vcmd = "RSH=\"$_[0]->{'rsh'}\" RMT=\"touch '$hfile'; /etc/rmt\" $vcmd";
$vcmd = "RSH=".quotemeta($_[0]->{'rsh'})." ".
"RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $vcmd";
}
else {
$vcmd = "RMT=\"touch '$hfile'; /etc/rmt\" $vcmd";
$vcmd = "RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $vcmd";
}
}
@@ -680,7 +699,7 @@ else {
}
if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'});
$cmd .= " -f '$in{'file'}'";
$cmd .= " -f ".quotemeta($in{'file'});
}
else {
&to_ipaddress($in{'host'}) ||
@@ -689,10 +708,10 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) {
$cmd .= " -f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'";
$cmd .= " -f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
}
else {
$cmd .= " -f '$in{'host'}:$in{'hfile'}'";
$cmd .= " -f ".quotemeta("$in{'host'}:$in{'hfile'}");
}
}
if ($_[0] eq 'tar') {
@@ -706,7 +725,7 @@ if ($_[0] eq 'tar') {
!-c $in{'file'} && !-b $in{'file'} ||
&error($text{'restore_emulti'});
$in{'mode'} == 0 || &error($text{'restore_emulti2'});
$cmd .= " -M -F \"$rmulti_cmd $in{'file'}\"";
$cmd .= " -M -F ".quotemeta("$rmulti_cmd $in{'file'}");
}
local $rsh = &rsh_command_parse("rsh_def", "rsh");
if ($rsh) {
@@ -716,15 +735,21 @@ if ($_[0] eq 'tar') {
$in{'rmt'} =~ /^\S+$/ || &error($text{'dump_ermt'});
$cmd .= " --rmt-command=".quotemeta($in{'rmt'});
}
$cmd .= " $in{'extra'}" if ($in{'extra'});
if ($in{'extra'} && !$access{'extra'}) {
&error($text{'restore_ecannot'});
}
my @extra = &split_shell_words($in{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
if (!$in{'files_def'}) {
$in{'files'} || &error($text{'restore_efiles'});
$cmd .= " $in{'files'}";
my @files = &split_shell_words($in{'files'});
@files || &error($text{'restore_efiles'});
$cmd .= " ".join(" ", map { quotemeta($_) } @files);
}
-d $in{'dir'} || &error($text{'restore_edir'});
$cmd = "cd '$in{'dir'}' && $cmd";
$cmd = "cd ".quotemeta($in{'dir'})." && $cmd";
if ($in{'multi'}) {
$cmd = "$rmulti_cmd $in{'file'} 1 && $cmd";
$cmd = "$rmulti_cmd ".quotemeta($in{'file'})." 1 && $cmd";
}
}
elsif ($_[0] eq 'xfs') {
@@ -732,19 +757,23 @@ elsif ($_[0] eq 'xfs') {
$cmd .= " -E" if ($in{'over'} == 1);
$cmd .= " -e" if ($in{'over'} == 2);
$cmd .= " -A" if ($in{'noattribs'});
$cmd .= " -L '$in{'label'}'" if ($in{'label'});
$cmd .= " -L ".quotemeta($in{'label'}) if ($in{'label'});
$cmd .= " -F";
$cmd .= " $in{'extra'}" if ($in{'extra'});
if ($in{'extra'} && !$access{'extra'}) {
&error($text{'restore_ecannot'});
}
my @extra = &split_shell_words($in{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
if (!$in{'test'}) {
-d $in{'dir'} || &error($text{'restore_edir'});
$cmd .= " '$in{'dir'}'";
$cmd .= " ".quotemeta($in{'dir'});
}
}
else {
# parse ext2/3 options
local $rsh = &rsh_command_parse("rsh_def", "rsh");
if ($rsh) {
$cmd = "RSH=\"$rsh\" $cmd";
$cmd = "RSH=".quotemeta($rsh)." $cmd";
}
if ($in{'multi'}) {
@@ -754,10 +783,16 @@ else {
}
}
$cmd .= " -u"; # force overwrite
$cmd .= " $in{'extra'}" if ($in{'extra'});
if ($in{'extra'} && !$access{'extra'}) {
&error($text{'restore_ecannot'});
}
my @extra = &split_shell_words($in{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
if (!$in{'files_def'}) {
$in{'files'} || &error($text{'restore_efiles'});
$cmd .= " $in{'files'}";
my @files = &split_shell_words($in{'files'});
@files || &error($text{'restore_efiles'});
$cmd .= " ".join(" ", map { quotemeta($_) } @files);
}
-d $in{'dir'} || &error($text{'restore_edir'});
}
@@ -782,7 +817,8 @@ if ($_[0] eq 'xfs') {
else {
# Need to supply prompts
&foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume;
while(1) {
local $rv = &wait_for($fh, "(.*next volume #)", "(.*set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "(.*enter volume name)", "password:", "yes\\/no", "(.*\\n)");

View File

@@ -75,6 +75,10 @@ else {
$dump->{'email'} = $in{'email_def'} ? '*' : $in{'email'};
$dump->{'subject'} = $in{'subject_def'} ? undef : $in{'subject'};
if ($access{'extra'}) {
if (defined($in{'extra'}) &&
$in{'extra'} =~ /[;&|`\$<>\r\n\0]/) {
&error("Invalid extra command-line parameters");
}
$dump->{'extra'} = $in{'extra'};
}
if ($access{'cmds'}) {

View File

@@ -92,20 +92,21 @@ $cmd .= "u" if ($_[0]->{'update'});
$cmd .= "v" if ($_[0]->{'verify'});
$cmd .= "o" if ($_[0]->{'offline'});
if ($_[0]->{'huser'}) {
$cmd .= "f '$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4])."'";
$cmd .= "f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
}
elsif ($_[0]->{'host'}) {
$cmd .= "f '$_[0]->{'host'}:".&date_subs($_[0]->{'hfile'}, $_[4])."'";
$cmd .= "f ".quotemeta("$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
}
else {
$cmd .= "f '".&date_subs($_[0]->{'file'}, $_[4])."'";
$cmd .= "f ".quotemeta(&date_subs($_[0]->{'file'}, $_[4]));
}
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
local @dirs = $_[0]->{'tabs'} ? split(/\t+/, $_[0]->{'dir'})
: split(/\s+/, $_[0]->{'dir'});
@dirs = map { &date_subs($_) } @dirs;
$cmd .= " ".join(" ", map { "'$_'" } @dirs);
$cmd .= " ".join(" ", map { quotemeta($_) } @dirs);
&system_logged("sync");
sleep(1);
@@ -182,7 +183,7 @@ $cmd = "ufsrestore";
$cmd .= ($in{'test'} ? " t" : " x");
if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'});
$cmd .= "f '$in{'file'}'";
$cmd .= "f ".quotemeta($in{'file'});
}
else {
&to_ipaddress($in{'host'}) ||
@@ -191,10 +192,10 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) {
$cmd .= "f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'";
$cmd .= "f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
}
else {
$cmd .= "f '$in{'host'}:$in{'hfile'}'";
$cmd .= "f ".quotemeta("$in{'host'}:$in{'hfile'}");
}
}
@@ -215,7 +216,8 @@ sub restore_backup
&additional_log('exec', undef, $_[1]);
&foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume;
while(1) {
local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "(Directories already exist, set modes anyway. \\[yn\\])", "((.*)\\[yn\\])", "(.*\\n)");

View File

@@ -893,7 +893,7 @@ body > .mode > b[data-mode="server-manager"] > a > .ff-cloudmin {
}
.field-sizing-content {
field-sizing: content !important;
min-width: 40px !important;
min-width: 15px !important;
}
.text-danger {
color: #bc0303;

View File

@@ -37,16 +37,6 @@ if (&foreign_exists($module) &&
}
}
# if any template variables were given as URL params, substitute them into the file
foreach my $k (keys %in) {
if ($k =~ /^tmpl_(\S+)$/i) {
$hash{$1} = $in{$k};
}
}
if (%hash) {
$help = &substitute_template($help, \%hash);
}
# find and replace the <header> section
if ($help =~ s/<header>([^<]+)<\/header>//i) {
&popup_header($1);

View File

@@ -5,10 +5,12 @@
require './init-lib.pl';
&ReadParse();
$access{'bootup'} == 1 || &error($text{'change_ecannot'});
my %ok_levels = map { $_, 1 } (&list_runlevels(), "S", "s");
$ok_levels{$in{'level'}} || &error($text{'change_ecannot'});
&ui_print_header(undef, $text{'change_title'}, "");
$cmd = "telinit '$in{'level'}'";
$cmd = "telinit ".quotemeta($in{'level'});
print "<p>",&text('change_cmd', $in{'level'}, "<tt>$cmd</tt>"),"<p>\n";
&system_logged("$cmd </dev/null >/dev/null 2>&1 &");
&webmin_log("telinit", $in{'level'});

View File

@@ -2816,14 +2816,15 @@ foreach my $a (@rv) {
return @rv;
}
=head2 create_launchd_agent(name, start-script, boot-flag)
=head2 create_launchd_agent(name, start-script, boot-flag, [load-now])
Creates a new my launchd agent
=cut
sub create_launchd_agent
{
my ($name, $start, $boot) = @_;
my ($name, $start, $boot, $load) = @_;
$load = 1 if (!defined($load));
my $file = "/Library/LaunchDaemons/".$name.".plist";
my $plist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n".
@@ -2846,8 +2847,10 @@ $plist .= "</plist>\n";
&open_lock_tempfile(PLIST, ">$file");
&print_tempfile(PLIST, $plist);
&close_tempfile(PLIST);
my $out = &backquote_logged("launchctl load ".quotemeta($file)." 2>&1");
&error("Failed to load plist : $out") if ($?);
if ($load) {
my $out = &backquote_logged("launchctl load ".quotemeta($file)." 2>&1");
&error("Failed to load plist : $out") if ($?);
}
}
=head2 delete_launchd_agent(name)

View File

@@ -17,7 +17,25 @@ foreach $a ('start', 'restart', 'condrestart', 'reload', 'status', 'stop') {
}
$action ||= 'stop';
&ui_print_header(undef, $text{'ss_'.$action}, "");
$cmd = $in{'file'}." ".$action;
# Only allow known init action files
my %ok_files;
foreach my $a (&list_actions()) {
my ($name) = split(/\s+/, $a);
my $file = $name =~ /^\// ? $name : "$config{'init_dir'}/$name";
$ok_files{$file} = 1;
}
foreach my $rl (&list_runlevels()) {
foreach my $w ("S", "K") {
foreach my $a (&runlevel_actions($rl, $w)) {
my ($order, $name) = split(/\s+/, $a);
my $file = "$config{'init_base'}/rc$rl.d/$w$order$name";
$ok_files{$file} = 1 if (-r $file);
}
}
}
$ok_files{$in{'file'}} || &error($text{'ss_ecannot'});
$cmd = quotemeta($in{'file'})." ".quotemeta($action);
# In case the action was Webmin
$SIG{'TERM'} = 'ignore';

View File

@@ -56,6 +56,27 @@ if ($product) {
quotemeta($product).".service >/dev/null 2>&1");
}
}
elsif ($init_mode eq "launchd") {
# Update or create launchd agent to use start init wrapper
my $name = &launchd_name($product);
my %miniserv;
&get_miniserv_config(\%miniserv);
my $want_boot = $miniserv{'atboot'} ? 1 : 0;
my @agents = &list_launchd_agents();
my ($agent) = grep { $_->{'name'} eq $name } @agents;
if ($agent) {
my $boot = $agent->{'boot'} ? 1 : 0;
&delete_launchd_agent($name);
&create_launchd_agent($name,
"$config_directory/.start-init --nofork",
$boot, 0);
}
elsif ($want_boot) {
&create_launchd_agent($name,
"$config_directory/.start-init --nofork",
1, 0);
}
}
elsif (-d "/etc/init.d") {
copy_source_dest("$root_directory/webmin-init", "/etc/init.d/$product");
system("chkconfig --add $product >/dev/null 2>&1");

View File

@@ -8,8 +8,15 @@ $| = 1;
$theme_no_table++;
&ui_print_header(undef, $text{'up_title'}, "");
# Validate connection name against configured connections
my @conf = &get_config();
my %ok_conns = map { $_->{'value'}, 1 }
grep { $_->{'name'} eq 'conn' && $_->{'value'} ne '%default' }
@conf;
$ok_conns{$in{'conn'}} || &error($text{'save_ename'});
# Try to connect
$cmd = "$config{'ipsec'} auto --up '$in{'conn'}'";
$cmd = "$config{'ipsec'} auto --up ".quotemeta($in{'conn'});
print "<b>",&text('up_cmd', "<tt>$cmd</tt>"),"</b>\n";
print "<pre>";
&foreign_require("proc", "proc-lib.pl");

View File

@@ -475,6 +475,14 @@ time_in_mins=In $1 minutes
time_in_sec=In $1 second
time_in_secs=In $1 seconds
time_now=Just now
time_second=second
time_seconds=seconds
time_minute=minute
time_minutes=minutes
time_hour=hour
time_hours=hours
time_day=day
time_days=days
defcert_error=Default $1 bundled SSL certificate is being used. It is highly advised to update default <tt>$2</tt> certificate before proceeding with login.

View File

@@ -344,7 +344,7 @@ foreach $c (@$conf) {
}
print TEMP map { "$_\n" } &directive_lines($_[0]);
close(TEMP);
&set_ownership_permissions(undef, undef, 0700, $temp);
&set_ownership_permissions(undef, undef, 0644, $temp);
local $out = &backquote_logged("$config{'logrotate'} -f $temp 2>&1");
return ($?, $out);
}

View File

@@ -1,5 +1,6 @@
skip_index=1
lines=100
include_context=0
others=1
reverse=1
log_any=0

View File

@@ -1,5 +1,6 @@
skip_index=Open log view on module load?,1,1-Yes,0-No
lines=Default number of lines to display,0,6
include_context=Context lines around each match,3,None
compressed=Include compressed logs in searches?,1,1-Yes,0-No
refresh=Seconds between log view refreshes,3,Never
others=Show logs from other modules?,1,1-Yes,0-No

View File

@@ -41,6 +41,8 @@ view_header3=Lines of $1
view_empty=Log file is empty
view_loading=Log file is being watched .. No new lines yet.
view_filter=Filter lines with text $1
view_filter_surround=With context
view_filter_regex=Use regex
view_filter_btn=Filter
save_efile='$1' is not a valid filename : $2

View File

@@ -56,6 +56,7 @@ my $fselect = shift;
my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'}) || 1000;
my $journalctl_cmd = &has_command('journalctl');
return () if (!$journalctl_cmd);
my $systemctl_cmd = &has_command('systemctl') || 'systemctl';
my $eflags = "";
$eflags = " --reverse" if ($config{'reverse'});
my $jver = &get_journalctl_version();
@@ -89,8 +90,8 @@ my (%ucache, %uread);
my $units_cache = "$module_config_directory/units.cache";
&read_file($units_cache, \%ucache);
if (!%ucache) {
my $out = &backquote_command("systemctl list-units --all --no-legend ".
"--no-pager");
my $out = &backquote_command(quotemeta($systemctl_cmd).
" list-units --all --no-legend --no-pager");
foreach my $line (split(/\r?\n/, $out)) {
$line =~ s/^[^a-z0-9\-\_\.]+//i;
my ($unit, $desc) = (split(/\s+/, $line, 5))[0, 4];
@@ -104,9 +105,10 @@ if ($fselect) {
my %units = %uread ? %uread : %ucache;
foreach my $u (sort keys %units) {
my $uname = $u;
my $qu = quotemeta($u);
$uname =~ s/\\x([0-9A-Fa-f]{2})/pack('H2', $1)/eg;
push(@rs, { 'cmd' => "$journalctl_cmd --lines ".
"$lines --unit $u",
"$lines --unit $qu",
'desc' => $uname,
'id' => "journal-a-$u", });
}
@@ -287,7 +289,8 @@ else {
sub get_journalctl_version
{
my $bin = &has_command('journalctl');
my $out = &backquote_command("\"$bin\" --version 2>&1");
return undef if (!$bin);
my $out = &backquote_command(quotemeta($bin)." --version 2>&1");
if ($out =~ /systemd\s+([0-9]+(?:\.[0-9A-Za-z\-\+]+)*)/) {
return $1;
}

View File

@@ -28,7 +28,7 @@ if ($in{'idx'} ne '') {
my @systemctl_cmds = &get_systemctl_cmds(1);
my ($log);
if ($in{'idx'} eq 'journal-u') {
($log) = grep { $_->{'cmd'} =~ /--unit\s+\w+/ }
($log) = grep { $_->{'cmd'} =~ /--unit\s+\S+/ }
@systemctl_cmds;
$in{'idx'} = $log->{'id'};
}
@@ -117,6 +117,11 @@ print "Refresh: $config{'refresh'}\r\n"
my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'});
my $jfilter = $in{'filter'} ? $in{'filter'} : "";
my $filter = $jfilter ? quotemeta($jfilter) : "";
my $include_surrounding = $in{'surrounding'} ? 1 : 0;
my $context_lines = $config{'include_context'} =~ /^\d+$/
? int($config{'include_context'}) : 0;
my $has_context = $include_surrounding && $context_lines > 0;
my $use_regex = $in{'regex'} ? 1 : 0;
my $reverse = $config{'reverse'} ? 1 : 0;
my $follow = $in{'since'} eq '--follow' ? 1 : 0;
my $no_navlinks = $in{'nonavlinks'} == 1 ? 1 : undef;
@@ -168,15 +173,18 @@ if (!$follow) {
if ($reverse) {
$tailcmd .= " | tac" if ($cmd !~ /journalctl/);
}
$eflag = $gconfig{'os_type'} =~ /-linux/ ? "-E" : "";
$dashflag = $gconfig{'os_type'} =~ /-linux/ ? "--" : "";
$dashflag = "--";
my $grep_mode = $use_regex ? "-E" : "-F";
if (@cats) {
my $fcmd;
my $context_opts = $has_context ? " -C $context_lines" : "";
if ($cmd =~ /journalctl/) {
$fcmd = "$cmd --grep $filter";
$fcmd = "$cmd | grep -a $grep_mode$context_opts ".
"$dashflag $filter";
}
else {
$fcmd = "$cat | grep -i -a $eflag $dashflag $filter ".
$fcmd = "$cat | grep -i -a $grep_mode$context_opts ".
"$dashflag $filter ".
"| $tailcmd";
}
open(my $output_fh, '>', \$safe_proc_out);
@@ -254,10 +262,14 @@ else {
}
// Update log viewer with new data from the server
(async function () {
const progressUrl =
"view_log_progress.cgi?idx=$in{'idx'}&filter=" +
"@{[&urlize($jfilter)]}&regex=$use_regex";
const logviewer_progress_abort = new AbortController();
const logDataElement = document.getElementById("logdata"),
response = await fetch("view_log_progress.cgi?idx=$in{'idx'}&filter=$jfilter",
{ signal: logviewer_progress_abort.signal }),
response = await fetch(
progressUrl,
{ signal: logviewer_progress_abort.signal }),
reader = response.body.getReader(),
decoder = new TextDecoder("utf-8"),
processText = async function () {
@@ -417,13 +429,30 @@ if ($follow) {
print &text('view_header3', "&nbsp;$sel"),"\n";
}
else {
print &text($text_view_header, "&nbsp;" . &ui_textbox("lines", $lines, 3), "&nbsp;$sel"),"\n";
print &text(
$text_view_header,
"&nbsp;" . &ui_textbox("lines", $lines, 3),
"&nbsp;$sel"),"\n";
}
print "&nbsp;&nbsp;&nbsp;&nbsp;\n";
print &text('view_filter', "&nbsp;" . &ui_textbox("filter", $in{'filter'}, 12)),"\n";
print &text(
'view_filter',
"&nbsp;" . &ui_textbox("filter", $in{'filter'}, 15)),"\n";
print "&nbsp;&nbsp;\n";
print &ui_submit($text{'view_filter_btn'});
print "&nbsp;\n";
print &ui_tag(
'span',
&ui_checkbox("regex", 1,
$text{'view_filter_regex'}, $use_regex),
{ style => "vertical-align: middle; margin-left: 2px;" });
if ($context_lines > 0 && !$follow) {
print "&nbsp;\n";
print &ui_tag(
'span',
&ui_checkbox("surrounding", 1,
$text{'view_filter_surround'}, $include_surrounding),
{ style => "vertical-align: middle;" });
}
print &ui_form_end(),"<br>\n";
}

View File

@@ -36,13 +36,19 @@ $log->{'cmd'} .= " --follow";
# Add filter to the command if present
my $filter = $in{'filter'} ? quotemeta($in{'filter'}) : "";
my $use_regex = $in{'regex'} ? 1 : 0;
my $readcmd = $log->{'cmd'};
if ($filter) {
$log->{'cmd'} .= " --grep $filter";
my $grep_flag = $use_regex ? "-E" : "-F";
$readcmd .= " | grep --line-buffered -a $grep_flag -- $filter";
}
# Open a pipe to the journalctl command
my $pid = open(my $fh, '-|', $log->{'cmd'}) ||
print &text('save_ecannot4', $log->{'cmd'}).": $!";
my $pid = open(my $fh, '-|', $readcmd);
if (!defined($pid)) {
print &text('save_ecannot4', $readcmd).": $!";
exit;
}
# Read and output the log
while (my $line = <$fh>) {

View File

@@ -51,6 +51,7 @@ spam_report=Report spam using,1,sa_learn-<tt>sa&#45;learn --spam</tt>,spamassass
line3.5=From address options,11
from_addr=<tt>From:</tt> address to use when sending email manually,3,From mailbox username
webmin_from=<tt>From:</tt> address to use when Webmin sends email,3,Default (<tt>webmin-noreply@<i>yourhost</i></tt>)
webmin_from_name=Real name to use in From: address,3,None
from_virtualmin=Get <tt>From:</tt> address from Virtualmin?,1,1-Yes,0-No
from_dom=Domain to use in <tt>From:</tt> address,3,System hostname
no_orig_ip=Include browser IP in <tt>X-Originating</tt>-IP header?,1,0-Yes,1-No

View File

@@ -600,16 +600,21 @@ return &ui_link("list_mail.cgi?user=$_[0]&folder=$_[1]->{'index'}",$text{'mail_r
# Returns the address to use when sending email from a script
sub get_from_address
{
local $host = &get_from_domain();
my $host = &get_from_domain();
my $rv;
if ($config{'webmin_from'} =~ /\@/) {
return $config{'webmin_from'};
$rv = $config{'webmin_from'};
}
elsif (!$config{'webmin_from'}) {
return "webmin-noreply\@$host";
$rv = "webmin-noreply\@$host";
}
else {
return "$config{'webmin_from'}\@$host";
$rv = "$config{'webmin_from'}\@$host";
}
if ($config{'webmin_from_name'}) {
$rv = "\"$config{'webmin_from_name'}\" <$rv>";
}
return $rv;
}
# get_from_domain()

View File

@@ -22,7 +22,7 @@ sub list_mailcap
{
if (!scalar(@list_mailcap_cache)) {
@list_mailcap_cache = ( );
open(CAP, "<".$mailcap_file);
open(CAP, "<", $mailcap_file);
local $lnum = 0;
while(<CAP>) {
local ($slnum, $elnum) = ($lnum, $lnum);

View File

@@ -109,7 +109,7 @@ if ($product eq "webmin") {
$size = int(`du -sk $tmp_dir`);
@deps = ( "perl", "libnet-ssleay-perl", "openssl", "libauthen-pam-perl", "libpam-runtime", "libio-pty-perl", "unzip", "shared-mime-info", "tar", "libdigest-sha-perl", "libdigest-md5-perl", "gzip" );
$deps = join(", ", @deps);
@recommends = ( "libdatetime-perl", "libdatetime-timezone-perl", "libdatetime-locale-perl", "libtime-piece-perl", "libencode-detect-perl", "libtime-hires-perl", "libsocket6-perl", "html2text", "qrencode", "libdbi-perl", "libdbd-mysql-perl", "libjson-xs-perl", "libsys-syslog-perl" );
@recommends = ( "libdatetime-perl", "libdatetime-timezone-perl", "libdatetime-locale-perl", "libtime-piece-perl", "libencode-detect-perl", "libtime-hires-perl", "libsocket6-perl", "html2text", "qrencode", "libdbi-perl", "libdbd-mysql-perl", "libdbd-mariadb-perl", "libjson-xs-perl", "libsys-syslog-perl" );
$recommends = join(", ", @recommends);
open(CONTROL, ">$control_file");
print CONTROL <<EOF;

View File

@@ -287,4 +287,3 @@ sub usage
{
die "Usage: $0 [--minimal] [--mod-list type] <version>\n";
}

View File

@@ -241,6 +241,7 @@ if ($< == 0) {
system("cd $usr_dir && chown -R root:bin .");
}
system("find $usr_dir -name .git | xargs rm -rf");
system("find $usr_dir -name .github | xargs rm -rf");
system("find $usr_dir -name RELEASE | xargs rm -rf");
system("find $usr_dir -name RELEASE.sh | xargs rm -rf");
if (-r "$usr_dir/$mod/EXCLUDE") {

Some files were not shown because too many files have changed in this diff Show More